2018-07-03 13:00:18 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
import datetime as dt
|
|
|
|
from io import StringIO
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
import pytz
|
|
|
|
|
2020-07-22 21:36:48 +02:00
|
|
|
import stix2.serialization
|
2018-07-03 13:00:18 +02:00
|
|
|
import stix2.utils
|
|
|
|
|
2019-01-29 16:52:59 +01:00
|
|
|
from .constants import IDENTITY_ID
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
amsterdam = pytz.timezone('Europe/Amsterdam')
|
|
|
|
eastern = pytz.timezone('US/Eastern')
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'dttm, timestamp', [
|
|
|
|
(dt.datetime(2017, 1, 1, tzinfo=pytz.utc), '2017-01-01T00:00:00Z'),
|
|
|
|
(amsterdam.localize(dt.datetime(2017, 1, 1)), '2016-12-31T23:00:00Z'),
|
|
|
|
(eastern.localize(dt.datetime(2017, 1, 1, 12, 34, 56)), '2017-01-01T17:34:56Z'),
|
|
|
|
(eastern.localize(dt.datetime(2017, 7, 1)), '2017-07-01T04:00:00Z'),
|
|
|
|
(dt.datetime(2017, 7, 1), '2017-07-01T00:00:00Z'),
|
|
|
|
(dt.datetime(2017, 7, 1, 0, 0, 0, 1), '2017-07-01T00:00:00.000001Z'),
|
|
|
|
(stix2.utils.STIXdatetime(2017, 7, 1, 0, 0, 0, 1, precision='millisecond'), '2017-07-01T00:00:00.000Z'),
|
|
|
|
(stix2.utils.STIXdatetime(2017, 7, 1, 0, 0, 0, 1, precision='second'), '2017-07-01T00:00:00Z'),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_timestamp_formatting(dttm, timestamp):
|
|
|
|
assert stix2.utils.format_datetime(dttm) == timestamp
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'timestamp, dttm', [
|
|
|
|
(dt.datetime(2017, 1, 1, 0, tzinfo=pytz.utc), dt.datetime(2017, 1, 1, 0, 0, 0, tzinfo=pytz.utc)),
|
|
|
|
(dt.date(2017, 1, 1), dt.datetime(2017, 1, 1, 0, 0, 0, tzinfo=pytz.utc)),
|
|
|
|
('2017-01-01T00:00:00Z', dt.datetime(2017, 1, 1, 0, 0, 0, tzinfo=pytz.utc)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_datetime(timestamp, dttm):
|
|
|
|
assert stix2.utils.parse_into_datetime(timestamp) == dttm
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'timestamp, dttm, precision', [
|
2020-05-20 21:06:53 +02:00
|
|
|
('2017-01-01T01:02:03.000001Z', dt.datetime(2017, 1, 1, 1, 2, 3, 0, tzinfo=pytz.utc), 'millisecond'),
|
|
|
|
('2017-01-01T01:02:03.001Z', dt.datetime(2017, 1, 1, 1, 2, 3, 1000, tzinfo=pytz.utc), 'millisecond'),
|
|
|
|
('2017-01-01T01:02:03.1Z', dt.datetime(2017, 1, 1, 1, 2, 3, 100000, tzinfo=pytz.utc), 'millisecond'),
|
|
|
|
('2017-01-01T01:02:03.45Z', dt.datetime(2017, 1, 1, 1, 2, 3, 450000, tzinfo=pytz.utc), 'millisecond'),
|
|
|
|
('2017-01-01T01:02:03.45Z', dt.datetime(2017, 1, 1, 1, 2, 3, tzinfo=pytz.utc), 'second'),
|
2018-07-13 17:10:05 +02:00
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_datetime_precision(timestamp, dttm, precision):
|
|
|
|
assert stix2.utils.parse_into_datetime(timestamp, precision) == dttm
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'ts', [
|
|
|
|
'foobar',
|
|
|
|
1,
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_datetime_invalid(ts):
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
stix2.utils.parse_into_datetime('foobar')
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'data', [
|
|
|
|
{"a": 1},
|
|
|
|
'{"a": 1}',
|
|
|
|
StringIO(u'{"a": 1}'),
|
2021-01-13 23:52:15 +01:00
|
|
|
[("a", 1)],
|
2018-07-13 17:10:05 +02:00
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_get_dict(data):
|
|
|
|
assert stix2.utils._get_dict(data)
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'data', [
|
|
|
|
1,
|
|
|
|
[1],
|
|
|
|
['a', 1],
|
|
|
|
"foobar",
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_get_dict_invalid(data):
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
stix2.utils._get_dict(data)
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'stix_id, type', [
|
|
|
|
('malware--d69c8146-ab35-4d50-8382-6fc80e641d43', 'malware'),
|
|
|
|
('intrusion-set--899ce53f-13a0-479b-a0e4-67d46e241542', 'intrusion-set'),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_get_type_from_id(stix_id, type):
|
|
|
|
assert stix2.utils.get_type_from_id(stix_id) == type
|
|
|
|
|
|
|
|
|
|
|
|
def test_deduplicate(stix_objs1):
|
|
|
|
unique = stix2.utils.deduplicate(stix_objs1)
|
|
|
|
|
2020-08-18 00:38:29 +02:00
|
|
|
# Only 4 objects are unique
|
|
|
|
# 3 id's vary
|
2018-07-03 13:00:18 +02:00
|
|
|
# 2 modified times vary for a particular id
|
|
|
|
|
2020-08-18 00:38:29 +02:00
|
|
|
assert len(unique) == 4
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
ids = [obj['id'] for obj in unique]
|
2020-08-18 00:38:29 +02:00
|
|
|
mods = [obj.get('modified') for obj in unique]
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2018-07-11 15:43:37 +02:00
|
|
|
assert "indicator--00000000-0000-4000-8000-000000000001" in ids
|
2020-08-18 00:38:29 +02:00
|
|
|
assert "indicator--00000000-0000-4000-8000-000000000002" in ids
|
|
|
|
assert "url--cc1deced-d99b-4d72-9268-8182420cb2fd" in ids
|
2018-07-03 13:00:18 +02:00
|
|
|
assert "2017-01-27T13:49:53.935Z" in mods
|
|
|
|
assert "2017-01-27T13:49:53.936Z" in mods
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'object, tuple_to_find, expected_index', [
|
|
|
|
(
|
|
|
|
stix2.v21.ObservedData(
|
|
|
|
id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
2019-01-29 16:52:59 +01:00
|
|
|
created_by_ref=IDENTITY_ID,
|
2018-07-13 17:10:05 +02:00
|
|
|
created="2016-04-06T19:58:16.000Z",
|
|
|
|
modified="2016-04-06T19:58:16.000Z",
|
|
|
|
first_observed="2015-12-21T19:00:00Z",
|
|
|
|
last_observed="2015-12-21T19:00:00Z",
|
|
|
|
number_observed=50,
|
|
|
|
objects={
|
|
|
|
"0": {
|
|
|
|
"name": "foo.exe",
|
|
|
|
"type": "file",
|
2019-08-30 09:47:47 +02:00
|
|
|
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
|
|
|
"1": {
|
|
|
|
"type": "ipv4-addr",
|
|
|
|
"value": "198.51.100.3",
|
2019-08-30 09:47:47 +02:00
|
|
|
"id": "ipv4-addr--1f8f4d63-9f33-5353-a3e3-e1b84c83a7b5",
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
|
|
|
"2": {
|
|
|
|
"type": "network-traffic",
|
2019-08-30 09:47:47 +02:00
|
|
|
"src_ref": "ipv4-addr--1f8f4d63-9f33-5353-a3e3-e1b84c83a7b5",
|
2018-07-13 17:10:05 +02:00
|
|
|
"protocols": [
|
|
|
|
"tcp",
|
|
|
|
"http",
|
|
|
|
],
|
|
|
|
"extensions": {
|
|
|
|
"http-request-ext": {
|
|
|
|
"request_method": "get",
|
|
|
|
"request_value": "/download.html",
|
|
|
|
"request_version": "http/1.1",
|
|
|
|
"request_header": {
|
|
|
|
"Accept-Encoding": "gzip,deflate",
|
|
|
|
"User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113",
|
|
|
|
"Host": "www.example.com",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-08-30 09:47:47 +02:00
|
|
|
), ('1', {"type": "ipv4-addr", "value": "198.51.100.3", "id": "ipv4-addr--1f8f4d63-9f33-5353-a3e3-e1b84c83a7b5"}), 1,
|
2018-07-13 17:10:05 +02:00
|
|
|
),
|
|
|
|
(
|
|
|
|
{
|
|
|
|
"type": "x-example",
|
|
|
|
"id": "x-example--d5413db2-c26c-42e0-b0e0-ec800a310bfb",
|
|
|
|
"created": "2018-06-11T01:25:22.063Z",
|
|
|
|
"modified": "2018-06-11T01:25:22.063Z",
|
|
|
|
"dictionary": {
|
|
|
|
"key": {
|
|
|
|
"key_one": "value",
|
|
|
|
"key_two": "value",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, ('key', {'key_one': 'value', 'key_two': 'value'}), 0,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
{
|
|
|
|
"type": "language-content",
|
|
|
|
"spec_version": "2.1",
|
|
|
|
"id": "language-content--b86bd89f-98bb-4fa9-8cb2-9ad421da981d",
|
|
|
|
"created": "2017-02-08T21:31:22.007Z",
|
|
|
|
"modified": "2017-02-08T21:31:22.007Z",
|
|
|
|
"object_ref": "campaign--12a111f0-b824-4baf-a224-83b80237a094",
|
|
|
|
"object_modified": "2017-02-08T21:31:22.007Z",
|
|
|
|
"contents": {
|
|
|
|
"de": {
|
|
|
|
"name": "Bank Angriff 1",
|
|
|
|
"description": "Weitere Informationen über Banküberfall",
|
|
|
|
},
|
|
|
|
"fr": {
|
|
|
|
"name": "Attaque Bank 1",
|
|
|
|
"description": "Plus d'informations sur la crise bancaire",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, ('fr', {"name": "Attaque Bank 1", "description": "Plus d'informations sur la crise bancaire"}), 1,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_find_property_index(object, tuple_to_find, expected_index):
|
2020-07-22 21:36:48 +02:00
|
|
|
assert stix2.serialization.find_property_index(
|
2018-07-03 13:00:18 +02:00
|
|
|
object,
|
|
|
|
*tuple_to_find
|
|
|
|
) == expected_index
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
'dict_value, tuple_to_find, expected_index', [
|
|
|
|
(
|
|
|
|
{
|
|
|
|
"contents": {
|
|
|
|
"de": {
|
|
|
|
"name": "Bank Angriff 1",
|
|
|
|
"description": "Weitere Informationen über Banküberfall",
|
|
|
|
},
|
|
|
|
"fr": {
|
|
|
|
"name": "Attaque Bank 1",
|
|
|
|
"description": "Plus d'informations sur la crise bancaire",
|
|
|
|
},
|
|
|
|
"es": {
|
|
|
|
"name": "Ataque al Banco",
|
|
|
|
"description": "Mas informacion sobre el ataque al banco",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, ('es', {"name": "Ataque al Banco", "description": "Mas informacion sobre el ataque al banco"}), 1,
|
|
|
|
), # Sorted alphabetically
|
|
|
|
(
|
|
|
|
{
|
|
|
|
'my_list': [
|
|
|
|
{"key_one": 1},
|
|
|
|
{"key_two": 2},
|
|
|
|
],
|
|
|
|
}, ('key_one', 1), 0,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_iterate_over_values(dict_value, tuple_to_find, expected_index):
|
2020-07-22 21:36:48 +02:00
|
|
|
assert stix2.serialization._find_property_in_seq(dict_value.values(), *tuple_to_find) == expected_index
|
2021-01-12 02:38:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"type_", [
|
2021-01-20 01:57:05 +01:00
|
|
|
"attack-pattern",
|
|
|
|
"campaign",
|
|
|
|
"course-of-action",
|
|
|
|
"identity",
|
|
|
|
"indicator",
|
|
|
|
"intrusion-set",
|
|
|
|
"malware",
|
|
|
|
"observed-data",
|
|
|
|
"report",
|
|
|
|
"threat-actor",
|
|
|
|
"tool",
|
|
|
|
"vulnerability",
|
|
|
|
|
|
|
|
# New in 2.1
|
2021-01-12 02:38:31 +01:00
|
|
|
"grouping",
|
|
|
|
"infrastructure",
|
|
|
|
"location",
|
|
|
|
"malware-analysis",
|
|
|
|
"note",
|
2021-01-21 02:49:01 +01:00
|
|
|
"opinion",
|
|
|
|
],
|
2021-01-12 02:38:31 +01:00
|
|
|
)
|
2021-01-20 01:57:05 +01:00
|
|
|
def test_is_sdo_dict(type_):
|
|
|
|
d = {
|
|
|
|
"type": type_,
|
2021-01-21 02:49:01 +01:00
|
|
|
"spec_version": "2.1",
|
2021-01-20 01:57:05 +01:00
|
|
|
}
|
|
|
|
assert stix2.utils.is_sdo(d, "2.1")
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"dict_", [
|
|
|
|
{"type": "software", "spec_version": "2.1"},
|
|
|
|
{"type": "software"},
|
|
|
|
{"type": "identity"},
|
|
|
|
{"type": "marking-definition", "spec_version": "2.1"},
|
|
|
|
{"type": "marking-definition"},
|
|
|
|
{"type": "bundle", "spec_version": "2.1"},
|
|
|
|
{"type": "bundle"},
|
|
|
|
{"type": "language-content", "spec_version": "2.1"},
|
|
|
|
{"type": "language-content"},
|
|
|
|
{"type": "relationship", "spec_version": "2.1"},
|
|
|
|
{"type": "relationship"},
|
|
|
|
{"type": "foo", "spec_version": "2.1"},
|
2021-01-21 02:49:01 +01:00
|
|
|
],
|
2021-01-20 01:57:05 +01:00
|
|
|
)
|
|
|
|
def test_is_not_sdo_dict(dict_):
|
|
|
|
assert not stix2.utils.is_sdo(dict_, "2.1")
|
2021-01-12 02:38:31 +01:00
|
|
|
|
|
|
|
|
2021-01-20 01:57:05 +01:00
|
|
|
def test_is_sco_dict():
|
2021-01-12 02:38:31 +01:00
|
|
|
d = {
|
2021-01-20 01:57:05 +01:00
|
|
|
"type": "file",
|
2021-01-21 02:49:01 +01:00
|
|
|
"spec_version": "2.1",
|
2021-01-12 02:38:31 +01:00
|
|
|
}
|
|
|
|
|
2021-01-20 01:57:05 +01:00
|
|
|
assert stix2.utils.is_sco(d, "2.1")
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"dict_", [
|
|
|
|
{"type": "identity"},
|
|
|
|
{"type": "identity", "spec_version": "2.1"},
|
|
|
|
{"type": "software"},
|
|
|
|
{"type": "marking-definition", "spec_version": "2.1"},
|
|
|
|
{"type": "marking-definition"},
|
|
|
|
{"type": "bundle", "spec_version": "2.1"},
|
|
|
|
{"type": "bundle"},
|
|
|
|
{"type": "language-content", "spec_version": "2.1"},
|
|
|
|
{"type": "language-content"},
|
|
|
|
{"type": "relationship", "spec_version": "2.1"},
|
|
|
|
{"type": "relationship"},
|
|
|
|
{"type": "foo", "spec_version": "2.1"},
|
2021-01-21 02:49:01 +01:00
|
|
|
],
|
2021-01-20 01:57:05 +01:00
|
|
|
)
|
|
|
|
def test_is_not_sco_dict(dict_):
|
|
|
|
assert not stix2.utils.is_sco(dict_, "2.1")
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"dict_", [
|
|
|
|
{"type": "relationship", "spec_version": "2.1"},
|
|
|
|
{"type": "sighting", "spec_version": "2.1"},
|
2021-01-21 02:49:01 +01:00
|
|
|
],
|
2021-01-20 01:57:05 +01:00
|
|
|
)
|
|
|
|
def test_is_sro_dict(dict_):
|
|
|
|
assert stix2.utils.is_sro(dict_, "2.1")
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"dict_", [
|
|
|
|
{"type": "identity", "spec_version": "2.1"},
|
|
|
|
{"type": "identity"},
|
|
|
|
{"type": "software", "spec_version": "2.1"},
|
|
|
|
{"type": "software"},
|
|
|
|
{"type": "marking-definition", "spec_version": "2.1"},
|
|
|
|
{"type": "marking-definition"},
|
|
|
|
{"type": "bundle", "spec_version": "2.1"},
|
|
|
|
{"type": "bundle"},
|
|
|
|
{"type": "language-content", "spec_version": "2.1"},
|
|
|
|
{"type": "language-content"},
|
|
|
|
{"type": "relationship"},
|
|
|
|
{"type": "sighting"},
|
|
|
|
{"type": "foo", "spec_version": "2.1"},
|
2021-01-21 02:49:01 +01:00
|
|
|
],
|
2021-01-20 01:57:05 +01:00
|
|
|
)
|
|
|
|
def test_is_not_sro_dict(dict_):
|
|
|
|
assert not stix2.utils.is_sro(dict_, "2.1")
|
2021-01-12 02:38:31 +01:00
|
|
|
|
|
|
|
|
2021-01-20 01:57:05 +01:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"dict_", [
|
|
|
|
{"type": "identity", "spec_version": "2.1"},
|
|
|
|
{"type": "software", "spec_version": "2.1"},
|
|
|
|
{"type": "marking-definition", "spec_version": "2.1"},
|
|
|
|
{"type": "language-content", "spec_version": "2.1"},
|
|
|
|
{
|
|
|
|
"type": "bundle",
|
|
|
|
"id": "bundle--8f431680-6278-4767-ba43-5edb682d7086",
|
|
|
|
"objects": [
|
|
|
|
{"type": "identity", "spec_version": "2.1"},
|
|
|
|
{"type": "software", "spec_version": "2.1"},
|
|
|
|
{"type": "marking-definition", "spec_version": "2.1"},
|
|
|
|
{"type": "language-content", "spec_version": "2.1"},
|
2021-01-21 02:49:01 +01:00
|
|
|
],
|
2021-01-20 01:57:05 +01:00
|
|
|
},
|
2021-01-21 02:49:01 +01:00
|
|
|
],
|
2021-01-20 01:57:05 +01:00
|
|
|
)
|
|
|
|
def test_is_object_dict(dict_):
|
|
|
|
assert stix2.utils.is_object(dict_, "2.1")
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"dict_", [
|
|
|
|
{"type": "identity"},
|
|
|
|
{"type": "software"},
|
|
|
|
{"type": "marking-definition"},
|
|
|
|
{"type": "bundle"},
|
|
|
|
{"type": "language-content"},
|
|
|
|
{"type": "relationship"},
|
|
|
|
{"type": "sighting"},
|
|
|
|
{"type": "foo"},
|
2021-01-21 02:49:01 +01:00
|
|
|
],
|
2021-01-20 01:57:05 +01:00
|
|
|
)
|
|
|
|
def test_is_not_object_dict(dict_):
|
|
|
|
assert not stix2.utils.is_object(dict_, "2.1")
|