def test_register_custom_object_with_version(): custom_obj_1 = { "type": "x-new-type-2", "id": "x-new-type-2--00000000-0000-4000-8000-000000000007", } cust_obj_1 = parsing.dict_to_stix2(custom_obj_1, version='2.0') v = 'v20' assert cust_obj_1.type in parsing.STIX2_OBJ_MAPS[v]['objects'] # spec_version is not in STIX 2.0, and is required in 2.1, so this # suffices as a test for a STIX 2.0 object. assert "spec_version" not in cust_obj_1
def decode_hook(self, dct: dict): type_ = dct.get("type", None) if not type_: return dct if type_ != SnapshotEnvelope.__name__.lower(): # decoders walk through nested objects first (bottom up). # try to parse nested stix2 objs per best-effort, else bubble up try: return dict_to_stix2(dct, allow_custom=True) except Exception: return dct return SnapshotEnvelope( MessageType(int(dct["snapshot_type"])), dct["snapshot_id"], parse(dct["body"], allow_custom=True), )
def test_dict_to_stix2_bundle_with_version(): with pytest.raises(exceptions.InvalidValueError) as excinfo: parsing.dict_to_stix2(BUNDLE, version='2.0') msg = "Invalid value for Bundle 'objects': Spec version 2.0 bundles don't yet support containing objects of a different spec version." assert str(excinfo.value) == msg
def test_dict_to_stix2_bundle_with_version(): with pytest.raises(exceptions.ExtraPropertiesError) as excinfo: parsing.dict_to_stix2(BUNDLE, version='2.1') assert str( excinfo.value) == "Unexpected properties for Bundle: (spec_version)."
def maltego_to_stix2( entity: MaltegoMsg, transform: MaltegoTransform = None, allow_custom_types=True, allow_custom_fields=True, allow_skipping_stix2_coercion=False ) -> Optional[Union[_STIXBase, dict]]: """ Try to convert any incoming Maltego entity to the closest corresponding STIX2 entity. If the input is a "proper" STIX2 entity (i.e. it is in the "maltego.STIX2." namespace), the recovered entity will be (at least nearly) equivalent to the STIX2 JSON object that would have been used to generate the Maltego entity. If the input is any other Maltego entity, a best guess is applied to generate an equivalent STIX2 object. If no matching STIX2 type is found, None is returned unless allow_custom_types is set to True. If allow_custom_types is used, this function will generate arbitrary STIX2-like objects will be generated (e.g. a maltego.Person entity will result in a 'maltego-person' STIX2 object. Properties will be translated according to an internally defined mapping. For instance, in Maltego, the STIX field "value" of "domain-name" is instead called "fqdn" on the "STIX2.domain-name" Entity. This mapping will be undone by this function, to translate the "fqdn" field back to "value". If allow_custom_fields is True, *all* fields will be added to the output object, :param entity: an incoming MaltegoMsg object (see maltego-trx library) :param transform: reference to the transform object (pass in 'response' from the calling transform). If missing, no logs will be sent to the client. :param allow_custom_types: if True, arbitrary new types may be returned regardless of input type, if False (default) only return a result where an "official" STIX2 types can be found. :param allow_custom_fields: if True, *all* properties on the Maltego entitiy will be added to the output STIX object, no matter if a mapping exists for it or not. If False (default), only properties that can be explicitly mapped are kept on the output STIX object. :param allow_skipping_stix2_coercion: if True, the final dict is directly returned without being converted to a _STIXBase object first. By default, this is turned off. :return: The resulting _STIXBase object, or the equivalent dictionary if skip_stix2_coercion is True (or None). """ is_proper_stix2 = False stix2_type = None for type_dict in entity.Genealogy: type_ = type_dict["Name"] if type_.startswith("maltego.STIX2."): is_proper_stix2 = True stix2_type = entity.Properties["type"] break elif type_ in _partial_reverse_type_map: stix2_type = _partial_reverse_type_map[type_] break if stix2_type is None: if allow_custom_types: stix2_type = (entity.Genealogy[0]["Name"].lower().replace( ".", "-")) else: return None res_dict = convert_maltego_property_bag_to_stix2( entity.Properties, stix2_type, allow_custom_fields=allow_custom_fields, assume_stix_input=is_proper_stix2) obj_type: Type[_STIXBase] = get_stix_type(res_dict) for prop_name, prop_def in obj_type._properties.items(): prop_value = res_dict.get(prop_name) if not prop_value: continue # TODO try to validate whether stix will accept the property. # If not, try some recovery, if it fails, just delete the prop valid = False try: prop_def.clean(res_dict[prop_name]) valid = True except: # TODO add more normalizers to recover normalizer = normalizers.get(prop_def.__class__) if normalizer: try: res_dict[prop_name] = normalizer(prop_value) valid = True except: pass if not valid: if allow_custom_fields: new_name = f"x_{prop_name}_unparseable" if transform is not None: transform.addUIMessage( f"Warning: STIX2 conversion of property {prop_name} failed, it was added as {new_name} instead", "PartialError") res_dict[new_name] = prop_value else: if transform is not None: transform.addUIMessage( f"Warning: STIX2 conversion of property {prop_name} failed and it was removed from the output", "PartialError") del res_dict[prop_name] # Handle default values mapping = _heritage_config.get(stix2_type) if mapping is not None and mapping.default_values is not None: for k, v in mapping.default_values.items(): if k not in res_dict: res_dict.update({k: v}) failed = True reason = None extra_reasons = [] stix2_object = res_dict try: stix2_object = dict_to_stix2(res_dict, allow_custom=allow_custom_types or allow_custom_fields) failed = False except MissingPropertiesError as e: reason = f"Missing properties error: {', '.join(e.args)}" for missing_prop_name in e.properties: if mapping is not None and missing_prop_name in mapping.property_map: maps_to = mapping.property_map[missing_prop_name] extra_reasons.append( f"Note: the property '{missing_prop_name}' is likely called '{maps_to}' on your input Entity " f"(and appears to be empty there).") except PropertyPresenceError as e: reason = f"Property presence error ({e.__class__}): {', '.join(e.args)}" except ObjectConfigurationError as e: if hasattr(e, "reason"): reason = f"Object configuration error: {e.reason}" else: reason = f"Unknown object configuration error" except Exception as e: reason = f"Unknown exception occurred." if failed: if allow_skipping_stix2_coercion: if transform is not None: transform.addUIMessage( f"Warning: Strict STIX2 conversion of object failed, " f"object will be returned as-is and may not be fully STIX2 compliant. " f"Reason of conversion failure: {reason}", "PartialError") else: if transform is not None: transform.addUIMessage( f"Error: Strict STIX2 conversion of object failed, no output will be returned. " f"Reason of failure: {reason}", "PartialError") stix2_object = None for r in extra_reasons: if transform is not None: transform.addUIMessage(r, "PartialError") return stix2_object