Repository: oasis-open/cti-python-stix2

Issue:
File SCO with WindowsPEBinary using WindowsPESection causes JSON serialization error
Below an example of how to trigger the problem:

``` python
from stix2.v21 import observables

directory_observable = observables.Directory(path="/home/user-1/")
file_observable = observables.File(
    name="example.exe",
    parent_directory_ref=directory_observable["id"],
    size=68 * 1000,
    magic_number_hex="50000000",
    hashes={
        "SHA-256": "841a8921140aba50671ebb0770fecc4ee308c4952cfeff8de154ab14eeef4649"
    },
    extensions={"windows-pebinary-ext": observables.WindowsPEBinaryExt(pe_type="exe", machine_hex="014c", sections=[observables.WindowsPESection(name=".data", size=4096, entropy=7.980693, hashes={"SHA-256": "6e3b6f3978e5cd96ba7abee35c24e867b7e64072e2ecb22d0ee7a6e6af6894d0"})])}
)
print(directory_observable, file_observable)
```

I obtain the following error:

```
Traceback (most recent call last):
  File "C:/Users/user-1/home/example_project/exe_report.py", line 69, in <module>
    exe_report()
  File "C:/Users/user-1/home/example_project/exe_report.py", line 45, in exe_report
    extensions={"windows-pebinary-ext": observables.WindowsPEBinaryExt(pe_type="exe", machine_hex="014c", sections=[observables.WindowsPESection(name=".data", size=4096, entropy=7.980693, hashes={"SHA-256": "6e3b6f3978e5cd96ba7abee35c24e867b7e64072e2ecb22d0ee7a6e6af6894d0"})])}
  File "C:\Users\user-1\venv\lib\site-packages\stix2\base.py", line 317, in __init__
    possible_id = self._generate_id(kwargs)
  File "C:\Users\user-1\venv\lib\site-packages\stix2\base.py", line 395, in _generate_id
    data = canonicalize(streamlined_obj_vals, utf8=False)
  File "C:\Users\user-1\venv\lib\site-packages\stix2\canonicalization\Canonicalize.py", line 502, in canonicalize
    textVal = JSONEncoder(sort_keys=True).encode(obj)
  File "C:\Users\user-1\venv\lib\site-packages\stix2\canonicalization\Canonicalize.py", line 233, in encode
    chunks = list(chunks)
  File "C:\Users\user-1\venv\lib\site-packages\stix2\canonicalization\Canonicalize.py", line 478, in _iterencode
    for thing in _iterencode_list(o, _current_indent_level):
  File "C:\Users\user-1\venv\lib\site-packages\stix2\canonicalization\Canonicalize.py", line 367, in _iterencode_list
    for chunk in chunks:
  File "C:\Users\user-1\venv\lib\site-packages\stix2\canonicalization\Canonicalize.py", line 450, in _iterencode_dict
    for chunk in chunks:
  File "C:\Users\user-1\venv\lib\site-packages\stix2\canonicalization\Canonicalize.py", line 450, in _iterencode_dict
    for chunk in chunks:
  File "C:\Users\user-1\venv\lib\site-packages\stix2\canonicalization\Canonicalize.py", line 367, in _iterencode_list
    for chunk in chunks:
  File "C:\Users\user-1\venv\lib\site-packages\stix2\canonicalization\Canonicalize.py", line 491, in _iterencode
    o = _default(o)
  File "C:\Users\user-1\venv\lib\site-packages\stix2\canonicalization\Canonicalize.py", line 211, in default
    o.__class__.__name__,
TypeError: Object of type 'WindowsPESection' is not JSON serializable
```

Discussion:

Patch:
diff --git a/stix2/base.py b/stix2/base.py
index a283902..4248075 100644
--- a/stix2/base.py
+++ b/stix2/base.py
@@ -388,14 +388,12 @@ class _Observable(_STIXBase):
                         temp_deep_copy = copy.deepcopy(dict(kwargs[key]))
                         _recursive_stix_to_dict(temp_deep_copy)
                         streamlined_obj_vals.append(temp_deep_copy)
-                    elif isinstance(kwargs[key], list) and isinstance(kwargs[key][0], _STIXBase):
-                        for obj in kwargs[key]:
-                            temp_deep_copy = copy.deepcopy(dict(obj))
-                            _recursive_stix_to_dict(temp_deep_copy)
-                            streamlined_obj_vals.append(temp_deep_copy)
+                    elif isinstance(kwargs[key], list):
+                        temp_deep_copy = copy.deepcopy(kwargs[key])
+                        _recursive_stix_list_to_dict(temp_deep_copy)
+                        streamlined_obj_vals.append(temp_deep_copy)
                     else:
                         streamlined_obj_vals.append(kwargs[key])
-
             if streamlined_obj_vals:
                 data = canonicalize(streamlined_obj_vals, utf8=False)
 
@@ -448,5 +446,20 @@ def _recursive_stix_to_dict(input_dict):
 
             # There may stil be nested _STIXBase objects
             _recursive_stix_to_dict(input_dict[key])
+        elif isinstance(input_dict[key], list):
+            _recursive_stix_list_to_dict(input_dict[key])
         else:
-            return
+            pass
+
+
+def _recursive_stix_list_to_dict(input_list):
+    for i in range(len(input_list)):
+        if isinstance(input_list[i], _STIXBase):
+            input_list[i] = dict(input_list[i])
+        elif isinstance(input_list[i], dict):
+            pass
+        elif isinstance(input_list[i], list):
+            _recursive_stix_list_to_dict(input_list[i])
+        else:
+            continue
+        _recursive_stix_to_dict(input_list[i])


Test patch:
diff --git a/stix2/test/v21/test_observed_data.py b/stix2/test/v21/test_observed_data.py
index 6f36d88..71bad46 100644
--- a/stix2/test/v21/test_observed_data.py
+++ b/stix2/test/v21/test_observed_data.py
@@ -1538,3 +1538,47 @@ def test_deterministic_id_no_contributing_props():
     uuid_obj_2 = uuid.UUID(email_msg_2.id[-36:])
     assert uuid_obj_2.variant == uuid.RFC_4122
     assert uuid_obj_2.version == 4
+
+
+def test_id_gen_recursive_dict_conversion_1():
+    file_observable = stix2.v21.File(
+        name="example.exe",
+        size=68 * 1000,
+        magic_number_hex="50000000",
+        hashes={
+            "SHA-256": "841a8921140aba50671ebb0770fecc4ee308c4952cfeff8de154ab14eeef4649",
+        },
+        extensions={
+            "windows-pebinary-ext": stix2.v21.WindowsPEBinaryExt(
+                pe_type="exe",
+                machine_hex="014c",
+                sections=[
+                    stix2.v21.WindowsPESection(
+                        name=".data",
+                        size=4096,
+                        entropy=7.980693,
+                        hashes={"SHA-256": "6e3b6f3978e5cd96ba7abee35c24e867b7e64072e2ecb22d0ee7a6e6af6894d0"},
+                    ),
+                ],
+            ),
+        },
+    )
+
+    assert file_observable.id == "file--5219d93d-13c1-5f1f-896b-039f10ec67ea"
+
+
+def test_id_gen_recursive_dict_conversion_2():
+    wrko = stix2.v21.WindowsRegistryKey(
+        values=[
+            stix2.v21.WindowsRegistryValueType(
+                name="Foo",
+                data="qwerty",
+            ),
+            stix2.v21.WindowsRegistryValueType(
+                name="Bar",
+                data="42",
+            ),
+        ],
+    )
+
+    assert wrko.id == "windows-registry-key--c087d9fe-a03e-5922-a1cd-da116e5b8a7b"