diff --git a/Makefile b/Makefile index 6e93756..0714c5a 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,12 @@ check: $(PYTEST) $(MYPY) install: $(VENV)/bin/pip install . +roundtrip: + PYTHONPATH=. $(VENV)/bin/python ./uswid/cli.py --roundtrip \ + --load ./examples/sample.ini \ + --save ./roundtrip.cdx.json \ + --verbose + blacken: $(BLACK) find uswid -name '*.py' -exec $(BLACK) {} \; diff --git a/examples/sample.ini b/examples/sample.ini index cbdb623..549a447 100644 --- a/examples/sample.ini +++ b/examples/sample.ini @@ -12,6 +12,7 @@ edition = v2024.07-rc1-246-gff97d147677b revision = 2.redhat persistent-id = org.hughsie.colorhug2.firmware cpe = cpe:2.3:a:hughski:colorhug:1.2.3:*:*:*:*:*:*:* +activation-status = DO NOT TRUST [uSWID-Entity:TagCreator] name = Richard Hughes diff --git a/uswid/cli.py b/uswid/cli.py index d6b9d88..93fd424 100755 --- a/uswid/cli.py +++ b/uswid/cli.py @@ -408,6 +408,7 @@ def _roundtrip(container: uSwidContainer, verbose: bool = False) -> None: "revision", "edition", "persistent_id", + "activation_status", "cpe", ]: if getattr(component, key) != getattr(component_new, key): diff --git a/uswid/component.py b/uswid/component.py index f3d3457..a712f46 100644 --- a/uswid/component.py +++ b/uswid/component.py @@ -129,6 +129,8 @@ def __init__( self.source_filenames: List[str] = [] """Top-level source directory for the project""" self.source_dir: Optional[str] = None + """Status, with specific terms and conditions for its use, e.g. 'DO NOT SHIP'""" + self.activation_status: Optional[str] = None def add_source_filename(self, source_file: str): """Adds a source filename, i.e. what file helped created this component""" @@ -204,6 +206,14 @@ def problems(self) -> List[uSwidProblem]: ] if not self.version_scheme: problems += [uSwidProblem("component", "No version scheme", since="0.4.7")] + if self.activation_status in ["DO NOT TRUST", "DO NOT SHIP"]: + problems += [ + uSwidProblem( + "component", + "Software should not be used in production", + since="0.5.1", + ) + ] if _is_redacted(self.summary): problems += [uSwidProblem("component", "Redacted summary", since="0.4.8")] diff --git a/uswid/format_coswid.py b/uswid/format_coswid.py index 986c1ce..e002027 100644 --- a/uswid/format_coswid.py +++ b/uswid/format_coswid.py @@ -246,6 +246,8 @@ def _save_component(self, component: uSwidComponent) -> bytes: metadata[uSwidGlobalMap.REVISION] = component.revision if component.product: metadata[uSwidGlobalMap.PRODUCT] = component.product + if component.activation_status: + metadata[uSwidGlobalMap.ACTIVATION_STATUS] = component.activation_status if component.edition: metadata[uSwidGlobalMap.EDITION] = _to_perhaps_hex_bytes(component.edition) if component.colloquial_version: @@ -421,6 +423,8 @@ def _load_component( component.revision = value elif key == uSwidGlobalMap.PRODUCT: component.product = value + elif key == uSwidGlobalMap.ACTIVATION_STATUS: + component.activation_status = value elif key == uSwidGlobalMap.EDITION: component.edition = _from_perhaps_hex_bytes(value) elif key == uSwidGlobalMap.COLLOQUIAL_VERSION: diff --git a/uswid/format_cyclonedx.py b/uswid/format_cyclonedx.py index aa0cca4..fda791e 100644 --- a/uswid/format_cyclonedx.py +++ b/uswid/format_cyclonedx.py @@ -131,6 +131,11 @@ def _load_component_internal( meta.get("value") ) + try: + component.activation_status = data["pedigree"]["notes"] + except KeyError: + pass + for hash_data in data.get("hashes", []): payload = uSwidPayload() payload.add_hash( @@ -376,6 +381,9 @@ def _save_component(self, component: uSwidComponent) -> Dict[str, Any]: if component.version_scheme: metadata["versionScheme"] = str(component.version_scheme) + if component.activation_status: + root["pedigree"] = {"notes": component.activation_status} + licenses: List[Dict[str, Any]] = [] for link in component.links: if not link.href: diff --git a/uswid/format_ini.py b/uswid/format_ini.py index 7451e76..e2529a0 100644 --- a/uswid/format_ini.py +++ b/uswid/format_ini.py @@ -143,6 +143,8 @@ def _save_component(self, component: uSwidComponent) -> bytes: main["colloquial-version"] = component.colloquial_version if component.persistent_id: main["persistent-id"] = component.persistent_id + if component.activation_status: + main["activation-status"] = component.activation_status if component.cpe: main["cpe"] = component.cpe config["uSWID"] = main @@ -306,6 +308,8 @@ def _load_component( component.colloquial_version = value elif key == "persistent-id": component.persistent_id = value + elif key == "activation-status": + component.activation_status = value elif key == "cpe": component.cpe = value else: diff --git a/uswid/format_swid.py b/uswid/format_swid.py index 36a3038..d67a446 100644 --- a/uswid/format_swid.py +++ b/uswid/format_swid.py @@ -180,6 +180,7 @@ def _save_component( or component.edition or component.colloquial_version or component.persistent_id + or component.activation_status ): node = ET.SubElement(root, "Meta") if component.summary: @@ -194,6 +195,8 @@ def _save_component( node.set("colloquialVersion", component.colloquial_version) if component.persistent_id: node.set("persistentId", component.persistent_id) + if component.activation_status: + node.set("activationStatus", component.activation_status) if component.cpe: node.set("cpe", component.cpe) if component.type: @@ -310,6 +313,7 @@ def _load_component(self, component: uSwidComponent, blob: bytes) -> None: ("edition", "edition"), ("colloquialVersion", "colloquial_version"), ("persistentId", "persistent_id"), + ("activationStatus", "activation_status"), ("cpe", "cpe"), ]: if attr_name in meta.attrib: