From 2a5c0cf1e4393e4dc753ea39892668d8fdd82d10 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Mon, 13 May 2024 12:08:33 -0500 Subject: [PATCH 01/31] committing current progress before rebase --- surfactant/input_readers/cyclonedx_reader.py | 59 ++++++++++++++++++++ surfactant/input_readers/spdx_reader.py | 19 +++++++ surfactant/output/cyclonedx_writer.py | 2 +- surfactant/plugin/manager.py | 6 +- 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 surfactant/input_readers/cyclonedx_reader.py create mode 100644 surfactant/input_readers/spdx_reader.py diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py new file mode 100644 index 00000000..09f3940f --- /dev/null +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -0,0 +1,59 @@ +import pathlib +from collections.abc import Iterable +from typing import Dict, List, Optional, Tuple +import json + +import cyclonedx.output +from cyclonedx.model import HashAlgorithm, HashType, OrganizationalEntity, Tool +from cyclonedx.model.bom import Bom, BomMetaData +from cyclonedx.model.bom_ref import BomRef +from cyclonedx.model.component import Component, ComponentType +from cyclonedx.model.dependency import Dependency + +import surfactant.plugin +from surfactant import __version__ as surfactant_version +from surfactant.sbomtypes import SBOM, Software, System + +# Copyright 2024 Lawrence Livermore National Security, LLC +# See the top-level LICENSE file for details. +# +# SPDX-License-Identifier: MIT + +from typing import Optional + +import surfactant.plugin +from surfactant.sbomtypes import SBOM + + +@surfactant.plugin.hookimpl +def read_sbom(infile) -> SBOM: + bom = Bom.from_json(data=json.loads(infile.read())) + sbom = SBOM() + + # Keep track of dependecies + # bom_ref -> xuuid, dependencies -> yuuids + # Keep track of which generated UUIDs map to which bom refs + for dependency in bom.dependencies: + print(dependency) + print(dependency.dependencies) + + # Create a CyTRICS software entry for each CycloneDX component + for component in bom.components: + print(component) + # If a component detail can be mapped to a detail in a software entry, then add to software entry details + # Otherwise, add detail to software entry's metadata section + # Add CycloneDX metadata section to metadata section of each software entry? + + + + return sbom + + +@surfactant.plugin.hookimpl +def short_name() -> Optional[str]: + return "cyclonedx" + +def convert_cyclonedx_components_to_software( + component: Component, +) -> List[Tuple[str, str, Software]]: + return \ No newline at end of file diff --git a/surfactant/input_readers/spdx_reader.py b/surfactant/input_readers/spdx_reader.py new file mode 100644 index 00000000..bf5fe4f7 --- /dev/null +++ b/surfactant/input_readers/spdx_reader.py @@ -0,0 +1,19 @@ +# Copyright 2023 Lawrence Livermore National Security, LLC +# See the top-level LICENSE file for details. +# +# SPDX-License-Identifier: MIT + +from typing import Optional + +import surfactant.plugin +from surfactant.sbomtypes import SBOM + + +@surfactant.plugin.hookimpl +def read_sbom(infile) -> SBOM: + return SBOM.from_json(infile.read()) + + +@surfactant.plugin.hookimpl +def short_name() -> Optional[str]: + return "spdx" diff --git a/surfactant/output/cyclonedx_writer.py b/surfactant/output/cyclonedx_writer.py index f8d95881..32408d23 100644 --- a/surfactant/output/cyclonedx_writer.py +++ b/surfactant/output/cyclonedx_writer.py @@ -316,4 +316,4 @@ def get_software_field(software: Software, field: str): for entry in software.metadata: if "FileInfo" in entry and "LegalCopyright" in entry["FileInfo"]: return entry["FileInfo"]["LegalCopyright"] - return None + return None \ No newline at end of file diff --git a/surfactant/plugin/manager.py b/surfactant/plugin/manager.py index 63b0ac2e..d2e79219 100644 --- a/surfactant/plugin/manager.py +++ b/surfactant/plugin/manager.py @@ -23,7 +23,10 @@ def _register_plugins(pm: pluggy.PluginManager) -> None: ole_file, pe_file, ) - from surfactant.input_readers import cytrics_reader + from surfactant.input_readers import ( + cytrics_reader, + cyclonedx_reader, + ) from surfactant.output import ( csv_writer, cyclonedx_writer, @@ -57,6 +60,7 @@ def _register_plugins(pm: pluggy.PluginManager) -> None: cyclonedx_writer, spdx_writer, cytrics_reader, + cyclonedx_reader, ) for plugin in internal_plugins: pm.register(plugin) From 1e95558b333ecf04348d7493bca6ed19b71fd293 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Mon, 10 Jun 2024 12:33:30 -0500 Subject: [PATCH 02/31] rebase --- surfactant/input_readers/cyclonedx_reader.py | 2 +- surfactant/output/cyclonedx_writer.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 09f3940f..43358a7e 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -4,7 +4,7 @@ import json import cyclonedx.output -from cyclonedx.model import HashAlgorithm, HashType, OrganizationalEntity, Tool +from cyclonedx.model import HashAlgorithm, HashType, Tool from cyclonedx.model.bom import Bom, BomMetaData from cyclonedx.model.bom_ref import BomRef from cyclonedx.model.component import Component, ComponentType diff --git a/surfactant/output/cyclonedx_writer.py b/surfactant/output/cyclonedx_writer.py index 32408d23..667c2e00 100644 --- a/surfactant/output/cyclonedx_writer.py +++ b/surfactant/output/cyclonedx_writer.py @@ -90,9 +90,9 @@ def write_sbom(sbom: SBOM, outfile) -> None: elif outformat == "xml": output_format = cyclonedx.output.OutputFormat.XML # The docs say that you don't need to specify a version (it says it defaults to the latest) - # but I got a missing keyword error when doing so, so just specify 1.5 for now + # but I got a missing keyword error when doing so, so just specify 1.6 for now outputter: cyclonedx.output.BaseOutput = cyclonedx.output.make_outputter( - bom=bom, output_format=output_format, schema_version=cyclonedx.schema.SchemaVersion.V1_5 + bom=bom, output_format=output_format, schema_version=cyclonedx.schema.SchemaVersion.V1_6 ) outfile.write(outputter.output_as_string()) From 12333fddd217ebecfa110bd69247a354bb7cea2b Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Mon, 17 Jun 2024 03:52:28 -0500 Subject: [PATCH 03/31] First draft of the CycloneDX reader hook --- surfactant/input_readers/cyclonedx_reader.py | 178 +++++++++++++++++-- 1 file changed, 162 insertions(+), 16 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 43358a7e..288ecae1 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -1,18 +1,14 @@ -import pathlib -from collections.abc import Iterable -from typing import Dict, List, Optional, Tuple +from typing import Dict, Optional, Tuple import json +import uuid -import cyclonedx.output -from cyclonedx.model import HashAlgorithm, HashType, Tool -from cyclonedx.model.bom import Bom, BomMetaData -from cyclonedx.model.bom_ref import BomRef -from cyclonedx.model.component import Component, ComponentType -from cyclonedx.model.dependency import Dependency +from cyclonedx.model import HashAlgorithm +from cyclonedx.model.bom import Bom +from cyclonedx.model.component import Component import surfactant.plugin from surfactant import __version__ as surfactant_version -from surfactant.sbomtypes import SBOM, Software, System +from surfactant.sbomtypes import SBOM, Software, SoftwareComponent, Relationship # Copyright 2024 Lawrence Livermore National Security, LLC # See the top-level LICENSE file for details. @@ -33,17 +29,51 @@ def read_sbom(infile) -> SBOM: # Keep track of dependecies # bom_ref -> xuuid, dependencies -> yuuids # Keep track of which generated UUIDs map to which bom refs - for dependency in bom.dependencies: - print(dependency) - print(dependency.dependencies) + uuids = {} + for xdependency in bom.dependencies: + #print(xdependency) + #print(xdependency.dependencies) + xbomref = xdependency.ref.value + if not xbomref in uuids.keys(): + new_uuid = str(uuid.uuid4()) + uuids[xbomref] = new_uuid + xuuid = uuids[xbomref] + # xuuid = xbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + + for ydependency in xdependency.dependencies: + ybomref = ydependency.ref.value + if not ybomref in uuids.keys(): + new_uuid = str(uuid.uuid4()) + uuids[ybomref] = new_uuid + yuuid = uuids[ybomref] + # yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + + # It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types + # and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being + # TODO: Add in other relationship type mappings + rel_type = "Contains" + cytrics_rel = Relationship(xUUID=xuuid,yUUID=yuuid,relationship=rel_type) + sbom.add_relationship(cytrics_rel) + + # print(sbom.relationships) # Create a CyTRICS software entry for each CycloneDX component for component in bom.components: - print(component) + # print(component) # If a component detail can be mapped to a detail in a software entry, then add to software entry details # Otherwise, add detail to software entry's metadata section # Add CycloneDX metadata section to metadata section of each software entry? + c_uuid, sw = convert_cyclonedx_components_to_software(component, uuids) + sbom.add_software(sw) + if component.bom_ref.value: + uuids[component.bom_ref.value] = c_uuid + # Do the same thing for the component from the CycloneDX metadata section (if there is one) because its bom-ref can appear in the dependencies + if bom.metadata.component: + mc_uuid, msw = convert_cyclonedx_components_to_software(bom.metadata.component, uuids) + sbom.add_software(msw) + if bom.metadata.component.bom_ref.value: + uuids[bom.metadata.component.bom_ref.value] = mc_uuid return sbom @@ -54,6 +84,122 @@ def short_name() -> Optional[str]: return "cyclonedx" def convert_cyclonedx_components_to_software( + component: Component, uuids: Dict +) -> Tuple[str, Software]: + print(component.bom_ref) + bomref = component.bom_ref.value + if (not bomref) or (not bomref in uuids.keys()): + cytrics_uuid = str(uuid.uuid4()) + else: + cytrics_uuid = uuids[bomref] + + # cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + + name = component.name + description = component.description + # CycloneDX only supports one supplier, so the vendor list will only contain one vendor + vendor = [component.supplier] + version = component.version + hashes = { + "SHA-1": None, + "SHA-256": None, + "MD5": None + } + for hash in component.hashes: + if hash.alg == HashAlgorithm.SHA_1: + hashes.update({"SHA-1": hash.content}) + elif hash.alg == HashAlgorithm.SHA_256: + hashes.update({"SHA-256": hash.content}) + elif hash.alg == HashAlgorithm.MD5: + hashes.update({"MD5": hash.content}) + + # Convert subcomponents of CycloneDX components into components of the corresponding CyTRICS software entry + sw_components = [] + for subcomp in component.components: + sw_comp = convert_cyclonedx_subcomponents_to_software_components(subcomp) + sw_components.append[sw_comp] + + # Add remaining data that is exclusive to CycloneDX component entries into the metadata section of the CyTRICS software entry + metadata = {} + if component.type: + metadata["type"] = component.type + if component.mime_type: + metadata["mime_type"] = component.mime_type + if component.publisher: + metadata["publisher"] = component.publisher + if component.group: + metadata["group"] = component.group + if component.scope: + metadata["scope"] = component.scope + if component.licenses: + metadata["licenses"] = component.licenses + if component.copyright: + metadata["copyright"] = component.copyright + if component.purl: + metadata["purl"] = component.purl + if component.external_references: + metadata["external_references"] = component.external_references + if component.properties: + metadata["properties"] = component.properties + if component.release_notes: + metadata["release_notes"] = component.release_notes + if component.cpe: + metadata["cpe"] = component.cpe + if component.swid: + metadata["swid"] = component.swid + if component.pedigree: + metadata["pedigree"] = component.pedigree + if component.evidence: + metadata["evidence"] = component.evidence + if component.modified: + metadata["modified"] = component.modified + if component.manufacturer: + metadata["manufacturer"] = component.manufacturer + if component.authors: + metadata["authors"] = component.authors + if component.omnibor_ids: + metadata["omnibor_ids"] = component.omnibor_ids + if component.swhids: + metadata["swhids"] = component.swhids + if component.crypto_properties: + metadata["crypto_properties"] = component.crypto_properties + if component.tags: + metadata["tags"] = component.tags + + # TODO: Is it possible to distinguish CycloneDX files from containers? + + sw_entry = Software( + UUID=cytrics_uuid, + name=name, + fileName="", + installPath=[""], + containerPath=[""], + version=version, + vendor=vendor, + description=description, + sha1=hashes["SHA-1"], + sha256=hashes["SHA-256"], + md5=hashes["MD5"], + metadata=metadata, + components=sw_components + ) + + return cytrics_uuid, sw_entry + +def convert_cyclonedx_subcomponents_to_software_components( component: Component, -) -> List[Tuple[str, str, Software]]: - return \ No newline at end of file +) -> SoftwareComponent: + name = component.name + description = component.description + # CycloneDX only supports one supplier, so vendor list will only contain one vendor + vendor = [component.supplier] + version = component.version + + sw_component = SoftwareComponent( + name=name, + version=version, + vendor=vendor, + description=description + ) + + return sw_component \ No newline at end of file From 0348efb9b1b78dc4a03a0647f17a6ce69e144f1d Mon Sep 17 00:00:00 2001 From: Marlon Schumacher <55295134+mws180000@users.noreply.github.com> Date: Mon, 17 Jun 2024 03:56:21 -0500 Subject: [PATCH 04/31] Deleting old file --- surfactant/input_readers/spdx_reader.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 surfactant/input_readers/spdx_reader.py diff --git a/surfactant/input_readers/spdx_reader.py b/surfactant/input_readers/spdx_reader.py deleted file mode 100644 index bf5fe4f7..00000000 --- a/surfactant/input_readers/spdx_reader.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2023 Lawrence Livermore National Security, LLC -# See the top-level LICENSE file for details. -# -# SPDX-License-Identifier: MIT - -from typing import Optional - -import surfactant.plugin -from surfactant.sbomtypes import SBOM - - -@surfactant.plugin.hookimpl -def read_sbom(infile) -> SBOM: - return SBOM.from_json(infile.read()) - - -@surfactant.plugin.hookimpl -def short_name() -> Optional[str]: - return "spdx" From 62ba77f9c0d838c79cb91a608a3f4f16fff48372 Mon Sep 17 00:00:00 2001 From: mws180000 Date: Tue, 18 Jun 2024 03:18:40 -0700 Subject: [PATCH 05/31] Added support for converting CycloneDX vulnerability entries into CyTRICS observations --- surfactant/input_readers/cyclonedx_reader.py | 111 +++++++++++++++---- 1 file changed, 91 insertions(+), 20 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 288ecae1..e69343cb 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -5,10 +5,11 @@ from cyclonedx.model import HashAlgorithm from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component +from cyclonedx.model.vulnerability import Vulnerability import surfactant.plugin from surfactant import __version__ as surfactant_version -from surfactant.sbomtypes import SBOM, Software, SoftwareComponent, Relationship +from surfactant.sbomtypes import SBOM, Software, SoftwareComponent, Relationship, Observation # Copyright 2024 Lawrence Livermore National Security, LLC # See the top-level LICENSE file for details. @@ -23,6 +24,18 @@ @surfactant.plugin.hookimpl def read_sbom(infile) -> SBOM: + """Reads the contents of the CycloneDX SBOM to the CyTRICS format. + + The read_sbom hook for the cyclonedx_reader makes a best-effort attempt + to map the information gathered from the CycloneDX file to a valid + internal SBOM representation. + + Args: + infile: The input file handle to read the CycloneDX SBOM from. + """ + # NOTE eventually informat should be user settable + informat = "json" + bom = Bom.from_json(data=json.loads(infile.read())) sbom = SBOM() @@ -32,14 +45,12 @@ def read_sbom(infile) -> SBOM: uuids = {} for xdependency in bom.dependencies: - #print(xdependency) - #print(xdependency.dependencies) xbomref = xdependency.ref.value if not xbomref in uuids.keys(): new_uuid = str(uuid.uuid4()) uuids[xbomref] = new_uuid xuuid = uuids[xbomref] - # xuuid = xbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + xuuid = xbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref for ydependency in xdependency.dependencies: ybomref = ydependency.ref.value @@ -47,33 +58,39 @@ def read_sbom(infile) -> SBOM: new_uuid = str(uuid.uuid4()) uuids[ybomref] = new_uuid yuuid = uuids[ybomref] - # yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref - # It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types - # and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being + """It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types + and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being""" # TODO: Add in other relationship type mappings rel_type = "Contains" cytrics_rel = Relationship(xUUID=xuuid,yUUID=yuuid,relationship=rel_type) sbom.add_relationship(cytrics_rel) - # print(sbom.relationships) # Create a CyTRICS software entry for each CycloneDX component for component in bom.components: - # print(component) - # If a component detail can be mapped to a detail in a software entry, then add to software entry details - # Otherwise, add detail to software entry's metadata section - # Add CycloneDX metadata section to metadata section of each software entry? - c_uuid, sw = convert_cyclonedx_components_to_software(component, uuids) + """If a component detail can be mapped to a detail in a software entry, then add to software entry details + Otherwise, add detail to software entry's metadata section""" + # Add CycloneDX metadata section to metadata section of each software entry + c_uuid, sw = convert_cyclonedx_component_to_software(component, uuids) sbom.add_software(sw) if component.bom_ref.value: uuids[component.bom_ref.value] = c_uuid # Do the same thing for the component from the CycloneDX metadata section (if there is one) because its bom-ref can appear in the dependencies if bom.metadata.component: - mc_uuid, msw = convert_cyclonedx_components_to_software(bom.metadata.component, uuids) + mc_uuid, msw = convert_cyclonedx_component_to_software(bom.metadata.component, uuids) sbom.add_software(msw) if bom.metadata.component.bom_ref.value: uuids[bom.metadata.component.bom_ref.value] = mc_uuid + + # Add vulnerabilities from the CycloneDX SBOM to the observations section in the CyTRICS SBOM + if bom.vulnerabilities: + for vuln in bom.vulnerabilities: + observation = convert_cyclonedx_vulnerability_to_observation(vuln) + sbom.observations.append(observation) + + return sbom @@ -83,9 +100,20 @@ def read_sbom(infile) -> SBOM: def short_name() -> Optional[str]: return "cyclonedx" -def convert_cyclonedx_components_to_software( +def convert_cyclonedx_component_to_software( component: Component, uuids: Dict ) -> Tuple[str, Software]: + """Converts a component entry in the CycloneDX SBOM to a CyTRICS software entry + + Args: + component (Component): The CycloneDX component to convert to a CyTRICS software entry. + uuids (Dict): A Python dictionary that keeps track of which CycloneDX bom-refs have already been assigned UUIDs + + Returns: + Tuple[str, Software]: A tuple containing the UUID of the Component that was + converted into a Software, and the Software object that was created. + """ + print(component.bom_ref) bomref = component.bom_ref.value if (not bomref) or (not bomref in uuids.keys()): @@ -93,10 +121,11 @@ def convert_cyclonedx_components_to_software( else: cytrics_uuid = uuids[bomref] - # cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref name = component.name description = component.description + # CycloneDX only supports one supplier, so the vendor list will only contain one vendor vendor = [component.supplier] version = component.version @@ -116,7 +145,7 @@ def convert_cyclonedx_components_to_software( # Convert subcomponents of CycloneDX components into components of the corresponding CyTRICS software entry sw_components = [] for subcomp in component.components: - sw_comp = convert_cyclonedx_subcomponents_to_software_components(subcomp) + sw_comp = convert_cyclonedx_subcomponent_to_software_components(subcomp) sw_components.append[sw_comp] # Add remaining data that is exclusive to CycloneDX component entries into the metadata section of the CyTRICS software entry @@ -186,9 +215,17 @@ def convert_cyclonedx_components_to_software( return cytrics_uuid, sw_entry -def convert_cyclonedx_subcomponents_to_software_components( - component: Component, +def convert_cyclonedx_subcomponent_to_software_components( + component: Component ) -> SoftwareComponent: + """Converts a subcomponent of a CycloneDX component into a component of the corresponding CyTRICS software entry + + Args: + component (Component): The subcomponent of the CycloneDX component to convert to a CyTRICS component in the CyTRICS software entry. + + Returns: + SoftwareComponent: The Software object that was created. + """ name = component.name description = component.description # CycloneDX only supports one supplier, so vendor list will only contain one vendor @@ -202,4 +239,38 @@ def convert_cyclonedx_subcomponents_to_software_components( description=description ) - return sw_component \ No newline at end of file + return sw_component + +def convert_cyclonedx_vulnerability_to_observation( + vulnerability: Vulnerability +) -> Observation: + """Convert a CycloneDX Vulnerability object into a CyTRICS Observation object + + Args: + vulnerability (Vulnerability): The vulnerability entry from the CycloneDX SBOM to convert to an observation entry in the CyTRICS SBOM. + + Returns: + Observation: The Observation object that was created. + """ + + vbomref = vulnerability.bom_ref.value + v_uuid = str(uuid.uuid4()) + v_uuid = vbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + cve = vulnerability.id + cvss = vulnerability.ratings.score + cwe = vulnerability.cwes + description = vulnerability.description + mitigations = vulnerability.recommendation + url = vulnerability.source.url + + sw_observation = Observation( + UUID=v_uuid, + CWEClass=cwe, + potentialEffectOrImpact=description, + CVE=cve, + CVSS=cvss, + toRecreate=url, + mitigationSuggestions=mitigations + ) + + return sw_observation \ No newline at end of file From 211741a77cf42b60f18cae8e9b42b8ced5253019 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 06:40:53 -0500 Subject: [PATCH 06/31] Made adding metadata more stable for now --- surfactant/input_readers/cyclonedx_reader.py | 75 ++++++++++---------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index e69343cb..5e66330c 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -5,7 +5,7 @@ from cyclonedx.model import HashAlgorithm from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component -from cyclonedx.model.vulnerability import Vulnerability +from cyclonedx.model.vulnerability import Vulnerability, VulnerabilityRating import surfactant.plugin from surfactant import __version__ as surfactant_version @@ -120,8 +120,7 @@ def convert_cyclonedx_component_to_software( cytrics_uuid = str(uuid.uuid4()) else: cytrics_uuid = uuids[bomref] - - cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref name = component.name description = component.description @@ -158,42 +157,42 @@ def convert_cyclonedx_component_to_software( metadata["publisher"] = component.publisher if component.group: metadata["group"] = component.group - if component.scope: - metadata["scope"] = component.scope - if component.licenses: - metadata["licenses"] = component.licenses + #if component.scope: + # metadata["scope"] = component.scope + #if component.licenses: + # metadata["licenses"] = component.licenses if component.copyright: metadata["copyright"] = component.copyright - if component.purl: - metadata["purl"] = component.purl - if component.external_references: - metadata["external_references"] = component.external_references - if component.properties: - metadata["properties"] = component.properties - if component.release_notes: - metadata["release_notes"] = component.release_notes + #if component.purl: + # metadata["purl"] = "pkg:" + component.purl.type + "/" + component.purl.namespace + "/" + component.purl.name + "@" + component.purl.version + "?" + component.purl.qualifiers + "#" + component.purl.subpath + #if component.external_references: + # metadata["external_references"] = component.external_references + #if component.properties: + # metadata["properties"] = component.properties + #if component.release_notes: + # metadata["release_notes"] = component.release_notes if component.cpe: metadata["cpe"] = component.cpe - if component.swid: - metadata["swid"] = component.swid - if component.pedigree: - metadata["pedigree"] = component.pedigree - if component.evidence: - metadata["evidence"] = component.evidence + #if component.swid: + # metadata["swid"] = component.swid + #if component.pedigree: + # metadata["pedigree"] = component.pedigree + #if component.evidence: + # metadata["evidence"] = component.evidence if component.modified: metadata["modified"] = component.modified - if component.manufacturer: - metadata["manufacturer"] = component.manufacturer - if component.authors: - metadata["authors"] = component.authors - if component.omnibor_ids: - metadata["omnibor_ids"] = component.omnibor_ids - if component.swhids: - metadata["swhids"] = component.swhids - if component.crypto_properties: - metadata["crypto_properties"] = component.crypto_properties - if component.tags: - metadata["tags"] = component.tags + #if component.manufacturer: + # metadata["manufacturer"] = component.manufacturer + #if component.authors: + # metadata["authors"] = component.authors + #if component.omnibor_ids: + # metadata["omnibor_ids"] = component.omnibor_ids + #if component.swhids: + # metadata["swhids"] = component.swhids + #if component.crypto_properties: + # metadata["crypto_properties"] = component.crypto_properties + #if component.tags: + # metadata["tags"] = component.tags # TODO: Is it possible to distinguish CycloneDX files from containers? @@ -255,13 +254,17 @@ def convert_cyclonedx_vulnerability_to_observation( vbomref = vulnerability.bom_ref.value v_uuid = str(uuid.uuid4()) - v_uuid = vbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + if vbomref: + v_uuid = vbomref # Comment this if statement if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref cve = vulnerability.id - cvss = vulnerability.ratings.score + cvss:int + for rating in vulnerability.ratings: + cvss = rating.score + break cwe = vulnerability.cwes description = vulnerability.description mitigations = vulnerability.recommendation - url = vulnerability.source.url + url = str(vulnerability.source.url) sw_observation = Observation( UUID=v_uuid, From 363211e1d1bbdc9df3d038c4be1bec98c970cd06 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 12:34:01 -0500 Subject: [PATCH 07/31] Updated metadata section --- surfactant/input_readers/cyclonedx_reader.py | 68 +++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 5e66330c..1c08a50a 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -4,8 +4,11 @@ from cyclonedx.model import HashAlgorithm from cyclonedx.model.bom import Bom -from cyclonedx.model.component import Component +from cyclonedx.model.component import Component, Swid, Pedigree, ComponentEvidence from cyclonedx.model.vulnerability import Vulnerability, VulnerabilityRating +from cyclonedx.model.release_note import ReleaseNotes +from cyclonedx.model.contact import OrganizationalContact, OrganizationalEntity +from cyclonedx.model.crypto import CryptoProperties import surfactant.plugin from surfactant import __version__ as surfactant_version @@ -158,43 +161,92 @@ def convert_cyclonedx_component_to_software( if component.group: metadata["group"] = component.group #if component.scope: + """ Need to see some examples of this property in use + TODO: Verify that this is serializable + """ # metadata["scope"] = component.scope #if component.licenses: + # TODO: Create a proper conversion of the object into a serializable format # metadata["licenses"] = component.licenses if component.copyright: metadata["copyright"] = component.copyright - #if component.purl: - # metadata["purl"] = "pkg:" + component.purl.type + "/" + component.purl.namespace + "/" + component.purl.name + "@" + component.purl.version + "?" + component.purl.qualifiers + "#" + component.purl.subpath + if component.purl: + purl = "pkg:" + component.purl.type + "/" + component.purl.namespace + "/" + component.purl.name + "@" + component.purl.version + if component.purl.qualifiers: + purl = purl + "?" + first = True + for qualifier in component.purl.qualifiers: + if first: + purl = purl + qualifier + "=" + component.purl.qualifiers[qualifier] + else: + purl = purl + "&" + qualifier + "=" + component.purl.qualifiers[qualifier] + if component.purl.subpath: + purl = purl + "#" + component.purl.subpath + + metadata["purl"] = purl #if component.external_references: + """*** Not JSON serializable on its own despite being a serializable class. + TODO: Create a proper conversion of the object into a serializable format + """ # metadata["external_references"] = component.external_references #if component.properties: + """*** Not JSON serializable on its own despite being a serializable class. + TODO: Create a proper conversion of the object into a serializable format + """ # metadata["properties"] = component.properties #if component.release_notes: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["release_notes"] = component.release_notes if component.cpe: metadata["cpe"] = component.cpe - #if component.swid: - # metadata["swid"] = component.swid + #if component.swid: + """*** Not JSON serializable on its own despite being a serializable class. + TODO: Create a proper conversion of the object into a serializable format + """ + # metadata["swid"] = str(component.swid) #if component.pedigree: + """*** Not JSON serializable on its own despite being a serializable class. + TODO: Create a proper conversion of the object into a serializable format + """ # metadata["pedigree"] = component.pedigree #if component.evidence: + """*** Not JSON serializable on its own despite being a serializable class. + TODO: Create a proper conversion of the object into a serializable format + """ # metadata["evidence"] = component.evidence if component.modified: metadata["modified"] = component.modified - #if component.manufacturer: - # metadata["manufacturer"] = component.manufacturer + if component.manufacturer: + metadata["manufacturer"] = component.manufacturer #if component.authors: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["authors"] = component.authors #if component.omnibor_ids: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["omnibor_ids"] = component.omnibor_ids #if component.swhids: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["swhids"] = component.swhids #if component.crypto_properties: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["crypto_properties"] = component.crypto_properties #if component.tags: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["tags"] = component.tags - # TODO: Is it possible to distinguish CycloneDX files from containers? + # TODO: Distinguish CycloneDX files from containers sw_entry = Software( UUID=cytrics_uuid, From 2458f382e855d0aeb4ec1cae298453034d631001 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:49:10 +0000 Subject: [PATCH 08/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- surfactant/input_readers/cyclonedx_reader.py | 157 +++++++++---------- surfactant/output/cyclonedx_writer.py | 2 +- surfactant/plugin/manager.py | 4 +- 3 files changed, 77 insertions(+), 86 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 1c08a50a..f406e8ab 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -1,36 +1,27 @@ -from typing import Dict, Optional, Tuple import json import uuid -from cyclonedx.model import HashAlgorithm -from cyclonedx.model.bom import Bom -from cyclonedx.model.component import Component, Swid, Pedigree, ComponentEvidence -from cyclonedx.model.vulnerability import Vulnerability, VulnerabilityRating -from cyclonedx.model.release_note import ReleaseNotes -from cyclonedx.model.contact import OrganizationalContact, OrganizationalEntity -from cyclonedx.model.crypto import CryptoProperties - -import surfactant.plugin -from surfactant import __version__ as surfactant_version -from surfactant.sbomtypes import SBOM, Software, SoftwareComponent, Relationship, Observation - # Copyright 2024 Lawrence Livermore National Security, LLC # See the top-level LICENSE file for details. # # SPDX-License-Identifier: MIT +from typing import Dict, Optional, Tuple -from typing import Optional +from cyclonedx.model import HashAlgorithm +from cyclonedx.model.bom import Bom +from cyclonedx.model.component import Component +from cyclonedx.model.vulnerability import Vulnerability import surfactant.plugin -from surfactant.sbomtypes import SBOM +from surfactant.sbomtypes import SBOM, Observation, Relationship, Software, SoftwareComponent @surfactant.plugin.hookimpl -def read_sbom(infile) -> SBOM: +def read_sbom(infile) -> SBOM: """Reads the contents of the CycloneDX SBOM to the CyTRICS format. The read_sbom hook for the cyclonedx_reader makes a best-effort attempt - to map the information gathered from the CycloneDX file to a valid + to map the information gathered from the CycloneDX file to a valid internal SBOM representation. Args: @@ -38,36 +29,36 @@ def read_sbom(infile) -> SBOM: """ # NOTE eventually informat should be user settable informat = "json" - + bom = Bom.from_json(data=json.loads(infile.read())) sbom = SBOM() - + # Keep track of dependecies # bom_ref -> xuuid, dependencies -> yuuids - # Keep track of which generated UUIDs map to which bom refs + # Keep track of which generated UUIDs map to which bom refs uuids = {} for xdependency in bom.dependencies: xbomref = xdependency.ref.value - if not xbomref in uuids.keys(): + if xbomref not in uuids.keys(): new_uuid = str(uuid.uuid4()) uuids[xbomref] = new_uuid xuuid = uuids[xbomref] - xuuid = xbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref - + xuuid = xbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + for ydependency in xdependency.dependencies: ybomref = ydependency.ref.value - if not ybomref in uuids.keys(): + if ybomref not in uuids.keys(): new_uuid = str(uuid.uuid4()) uuids[ybomref] = new_uuid yuuid = uuids[ybomref] - yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref """It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being""" # TODO: Add in other relationship type mappings rel_type = "Contains" - cytrics_rel = Relationship(xUUID=xuuid,yUUID=yuuid,relationship=rel_type) + cytrics_rel = Relationship(xUUID=xuuid, yUUID=yuuid, relationship=rel_type) sbom.add_relationship(cytrics_rel) # Create a CyTRICS software entry for each CycloneDX component @@ -79,23 +70,20 @@ def read_sbom(infile) -> SBOM: sbom.add_software(sw) if component.bom_ref.value: uuids[component.bom_ref.value] = c_uuid - + # Do the same thing for the component from the CycloneDX metadata section (if there is one) because its bom-ref can appear in the dependencies if bom.metadata.component: mc_uuid, msw = convert_cyclonedx_component_to_software(bom.metadata.component, uuids) sbom.add_software(msw) if bom.metadata.component.bom_ref.value: uuids[bom.metadata.component.bom_ref.value] = mc_uuid - + # Add vulnerabilities from the CycloneDX SBOM to the observations section in the CyTRICS SBOM if bom.vulnerabilities: for vuln in bom.vulnerabilities: observation = convert_cyclonedx_vulnerability_to_observation(vuln) sbom.observations.append(observation) - - - return sbom @@ -103,6 +91,7 @@ def read_sbom(infile) -> SBOM: def short_name() -> Optional[str]: return "cyclonedx" + def convert_cyclonedx_component_to_software( component: Component, uuids: Dict ) -> Tuple[str, Software]: @@ -119,23 +108,19 @@ def convert_cyclonedx_component_to_software( print(component.bom_ref) bomref = component.bom_ref.value - if (not bomref) or (not bomref in uuids.keys()): + if (not bomref) or (bomref not in uuids.keys()): cytrics_uuid = str(uuid.uuid4()) else: cytrics_uuid = uuids[bomref] - cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref name = component.name description = component.description - + # CycloneDX only supports one supplier, so the vendor list will only contain one vendor vendor = [component.supplier] version = component.version - hashes = { - "SHA-1": None, - "SHA-256": None, - "MD5": None - } + hashes = {"SHA-1": None, "SHA-256": None, "MD5": None} for hash in component.hashes: if hash.alg == HashAlgorithm.SHA_1: hashes.update({"SHA-1": hash.content}) @@ -143,13 +128,13 @@ def convert_cyclonedx_component_to_software( hashes.update({"SHA-256": hash.content}) elif hash.alg == HashAlgorithm.MD5: hashes.update({"MD5": hash.content}) - + # Convert subcomponents of CycloneDX components into components of the corresponding CyTRICS software entry sw_components = [] for subcomp in component.components: sw_comp = convert_cyclonedx_subcomponent_to_software_components(subcomp) sw_components.append[sw_comp] - + # Add remaining data that is exclusive to CycloneDX component entries into the metadata section of the CyTRICS software entry metadata = {} if component.type: @@ -160,18 +145,27 @@ def convert_cyclonedx_component_to_software( metadata["publisher"] = component.publisher if component.group: metadata["group"] = component.group - #if component.scope: + # if component.scope: """ Need to see some examples of this property in use TODO: Verify that this is serializable """ # metadata["scope"] = component.scope - #if component.licenses: - # TODO: Create a proper conversion of the object into a serializable format + # if component.licenses: + # TODO: Create a proper conversion of the object into a serializable format # metadata["licenses"] = component.licenses if component.copyright: metadata["copyright"] = component.copyright if component.purl: - purl = "pkg:" + component.purl.type + "/" + component.purl.namespace + "/" + component.purl.name + "@" + component.purl.version + purl = ( + "pkg:" + + component.purl.type + + "/" + + component.purl.namespace + + "/" + + component.purl.name + + "@" + + component.purl.version + ) if component.purl.qualifiers: purl = purl + "?" first = True @@ -184,35 +178,35 @@ def convert_cyclonedx_component_to_software( purl = purl + "#" + component.purl.subpath metadata["purl"] = purl - #if component.external_references: - """*** Not JSON serializable on its own despite being a serializable class. + # if component.external_references: + """*** Not JSON serializable on its own despite being a serializable class. TODO: Create a proper conversion of the object into a serializable format """ - # metadata["external_references"] = component.external_references - #if component.properties: - """*** Not JSON serializable on its own despite being a serializable class. + # metadata["external_references"] = component.external_references + # if component.properties: + """*** Not JSON serializable on its own despite being a serializable class. TODO: Create a proper conversion of the object into a serializable format """ - # metadata["properties"] = component.properties - #if component.release_notes: + # metadata["properties"] = component.properties + # if component.release_notes: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ # metadata["release_notes"] = component.release_notes if component.cpe: metadata["cpe"] = component.cpe - #if component.swid: - """*** Not JSON serializable on its own despite being a serializable class. + # if component.swid: + """*** Not JSON serializable on its own despite being a serializable class. TODO: Create a proper conversion of the object into a serializable format """ - # metadata["swid"] = str(component.swid) - #if component.pedigree: - """*** Not JSON serializable on its own despite being a serializable class. + # metadata["swid"] = str(component.swid) + # if component.pedigree: + """*** Not JSON serializable on its own despite being a serializable class. TODO: Create a proper conversion of the object into a serializable format """ - # metadata["pedigree"] = component.pedigree - #if component.evidence: - """*** Not JSON serializable on its own despite being a serializable class. + # metadata["pedigree"] = component.pedigree + # if component.evidence: + """*** Not JSON serializable on its own despite being a serializable class. TODO: Create a proper conversion of the object into a serializable format """ # metadata["evidence"] = component.evidence @@ -220,27 +214,27 @@ def convert_cyclonedx_component_to_software( metadata["modified"] = component.modified if component.manufacturer: metadata["manufacturer"] = component.manufacturer - #if component.authors: + # if component.authors: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ - # metadata["authors"] = component.authors - #if component.omnibor_ids: + # metadata["authors"] = component.authors + # if component.omnibor_ids: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ - # metadata["omnibor_ids"] = component.omnibor_ids - #if component.swhids: + # metadata["omnibor_ids"] = component.omnibor_ids + # if component.swhids: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ - # metadata["swhids"] = component.swhids - #if component.crypto_properties: + # metadata["swhids"] = component.swhids + # if component.crypto_properties: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ - # metadata["crypto_properties"] = component.crypto_properties - #if component.tags: + # metadata["crypto_properties"] = component.crypto_properties + # if component.tags: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ @@ -261,13 +255,14 @@ def convert_cyclonedx_component_to_software( sha256=hashes["SHA-256"], md5=hashes["MD5"], metadata=metadata, - components=sw_components + components=sw_components, ) return cytrics_uuid, sw_entry + def convert_cyclonedx_subcomponent_to_software_components( - component: Component + component: Component, ) -> SoftwareComponent: """Converts a subcomponent of a CycloneDX component into a component of the corresponding CyTRICS software entry @@ -284,17 +279,13 @@ def convert_cyclonedx_subcomponent_to_software_components( version = component.version sw_component = SoftwareComponent( - name=name, - version=version, - vendor=vendor, - description=description + name=name, version=version, vendor=vendor, description=description ) return sw_component -def convert_cyclonedx_vulnerability_to_observation( - vulnerability: Vulnerability -) -> Observation: + +def convert_cyclonedx_vulnerability_to_observation(vulnerability: Vulnerability) -> Observation: """Convert a CycloneDX Vulnerability object into a CyTRICS Observation object Args: @@ -303,13 +294,13 @@ def convert_cyclonedx_vulnerability_to_observation( Returns: Observation: The Observation object that was created. """ - + vbomref = vulnerability.bom_ref.value v_uuid = str(uuid.uuid4()) if vbomref: - v_uuid = vbomref # Comment this if statement if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + v_uuid = vbomref # Comment this if statement if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref cve = vulnerability.id - cvss:int + cvss: int for rating in vulnerability.ratings: cvss = rating.score break @@ -325,7 +316,7 @@ def convert_cyclonedx_vulnerability_to_observation( CVE=cve, CVSS=cvss, toRecreate=url, - mitigationSuggestions=mitigations + mitigationSuggestions=mitigations, ) - return sw_observation \ No newline at end of file + return sw_observation diff --git a/surfactant/output/cyclonedx_writer.py b/surfactant/output/cyclonedx_writer.py index 667c2e00..1a314f54 100644 --- a/surfactant/output/cyclonedx_writer.py +++ b/surfactant/output/cyclonedx_writer.py @@ -316,4 +316,4 @@ def get_software_field(software: Software, field: str): for entry in software.metadata: if "FileInfo" in entry and "LegalCopyright" in entry["FileInfo"]: return entry["FileInfo"]["LegalCopyright"] - return None \ No newline at end of file + return None diff --git a/surfactant/plugin/manager.py b/surfactant/plugin/manager.py index d2e79219..143f2124 100644 --- a/surfactant/plugin/manager.py +++ b/surfactant/plugin/manager.py @@ -24,9 +24,9 @@ def _register_plugins(pm: pluggy.PluginManager) -> None: pe_file, ) from surfactant.input_readers import ( - cytrics_reader, cyclonedx_reader, - ) + cytrics_reader, + ) from surfactant.output import ( csv_writer, cyclonedx_writer, From 4b17149ed7d0dbad0dfec709cf9b679983b2eef3 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 13:06:17 -0500 Subject: [PATCH 09/31] pre-commit fixes --- surfactant/input_readers/cyclonedx_reader.py | 25 +++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 1c08a50a..949d7188 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -1,14 +1,11 @@ -from typing import Dict, Optional, Tuple +from typing import Dict, Optional, Tuple, List import json import uuid from cyclonedx.model import HashAlgorithm from cyclonedx.model.bom import Bom -from cyclonedx.model.component import Component, Swid, Pedigree, ComponentEvidence -from cyclonedx.model.vulnerability import Vulnerability, VulnerabilityRating -from cyclonedx.model.release_note import ReleaseNotes -from cyclonedx.model.contact import OrganizationalContact, OrganizationalEntity -from cyclonedx.model.crypto import CryptoProperties +from cyclonedx.model.component import Component +from cyclonedx.model.vulnerability import Vulnerability import surfactant.plugin from surfactant import __version__ as surfactant_version @@ -136,16 +133,16 @@ def convert_cyclonedx_component_to_software( "SHA-256": None, "MD5": None } - for hash in component.hashes: - if hash.alg == HashAlgorithm.SHA_1: - hashes.update({"SHA-1": hash.content}) - elif hash.alg == HashAlgorithm.SHA_256: - hashes.update({"SHA-256": hash.content}) - elif hash.alg == HashAlgorithm.MD5: - hashes.update({"MD5": hash.content}) + for c_hash in component.hashes: + if c_hash.alg == HashAlgorithm.SHA_1: + hashes.update({"SHA-1": c_hash.content}) + elif c_hash.alg == HashAlgorithm.SHA_256: + hashes.update({"SHA-256": c_hash.content}) + elif c_hash.alg == HashAlgorithm.MD5: + hashes.update({"MD5": c_hash.content}) # Convert subcomponents of CycloneDX components into components of the corresponding CyTRICS software entry - sw_components = [] + sw_components: List[SoftwareComponent] = [] for subcomp in component.components: sw_comp = convert_cyclonedx_subcomponent_to_software_components(subcomp) sw_components.append[sw_comp] From 597d63d12ddd81951c267566f643cdd0f70c922a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:11:47 +0000 Subject: [PATCH 10/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- surfactant/input_readers/cyclonedx_reader.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 58b3244a..d991a87c 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -1,21 +1,11 @@ -from typing import Dict, Optional, Tuple, List import json import uuid -from cyclonedx.model import HashAlgorithm -from cyclonedx.model.bom import Bom -from cyclonedx.model.component import Component -from cyclonedx.model.vulnerability import Vulnerability - -import surfactant.plugin -from surfactant import __version__ as surfactant_version -from surfactant.sbomtypes import SBOM, Software, SoftwareComponent, Relationship, Observation - # Copyright 2024 Lawrence Livermore National Security, LLC # See the top-level LICENSE file for details. # # SPDX-License-Identifier: MIT -from typing import Dict, Optional, Tuple +from typing import Dict, List, Optional, Tuple from cyclonedx.model import HashAlgorithm from cyclonedx.model.bom import Bom @@ -130,11 +120,7 @@ def convert_cyclonedx_component_to_software( # CycloneDX only supports one supplier, so the vendor list will only contain one vendor vendor = [component.supplier] version = component.version - hashes = { - "SHA-1": None, - "SHA-256": None, - "MD5": None - } + hashes = {"SHA-1": None, "SHA-256": None, "MD5": None} for c_hash in component.hashes: if c_hash.alg == HashAlgorithm.SHA_1: hashes.update({"SHA-1": c_hash.content}) @@ -142,7 +128,7 @@ def convert_cyclonedx_component_to_software( hashes.update({"SHA-256": c_hash.content}) elif c_hash.alg == HashAlgorithm.MD5: hashes.update({"MD5": c_hash.content}) - + # Convert subcomponents of CycloneDX components into components of the corresponding CyTRICS software entry sw_components: List[SoftwareComponent] = [] for subcomp in component.components: From 1edf207f185cb763733edc21481e9e192c4055dc Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 13:24:42 -0500 Subject: [PATCH 11/31] pre-commit fix --- surfactant/input_readers/cyclonedx_reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 58b3244a..493e84e9 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -147,7 +147,7 @@ def convert_cyclonedx_component_to_software( sw_components: List[SoftwareComponent] = [] for subcomp in component.components: sw_comp = convert_cyclonedx_subcomponent_to_software_components(subcomp) - sw_components.append[sw_comp] + sw_components.append(sw_comp) # Add remaining data that is exclusive to CycloneDX component entries into the metadata section of the CyTRICS software entry metadata = {} From fe8671171f7beea3e35029580925a0c382a4f80c Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 13:34:06 -0500 Subject: [PATCH 12/31] precommit-fix --- surfactant/input_readers/cyclonedx_reader.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 31e91fc4..71f782d4 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -55,7 +55,8 @@ def read_sbom(infile) -> SBOM: yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref """It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types - and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being""" + and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being + """ # TODO: Add in other relationship type mappings rel_type = "Contains" cytrics_rel = Relationship(xUUID=xuuid, yUUID=yuuid, relationship=rel_type) @@ -64,7 +65,8 @@ def read_sbom(infile) -> SBOM: # Create a CyTRICS software entry for each CycloneDX component for component in bom.components: """If a component detail can be mapped to a detail in a software entry, then add to software entry details - Otherwise, add detail to software entry's metadata section""" + Otherwise, add detail to software entry's metadata section + """ # Add CycloneDX metadata section to metadata section of each software entry c_uuid, sw = convert_cyclonedx_component_to_software(component, uuids) sbom.add_software(sw) From ae4f5c1941622ee447986e5698068d8f5db69b9d Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 15:32:00 -0500 Subject: [PATCH 13/31] pre-commit fixes --- surfactant/input_readers/cyclonedx_reader.py | 125 ++++++++++--------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 71f782d4..ad5aee48 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -54,9 +54,9 @@ def read_sbom(infile) -> SBOM: yuuid = uuids[ybomref] yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref - """It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types - and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being - """ + # It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types + # and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being + # TODO: Add in other relationship type mappings rel_type = "Contains" cytrics_rel = Relationship(xUUID=xuuid, yUUID=yuuid, relationship=rel_type) @@ -64,9 +64,9 @@ def read_sbom(infile) -> SBOM: # Create a CyTRICS software entry for each CycloneDX component for component in bom.components: - """If a component detail can be mapped to a detail in a software entry, then add to software entry details - Otherwise, add detail to software entry's metadata section - """ + # If a component detail can be mapped to a detail in a software entry, then add to software entry details + # Otherwise, add detail to software entry's metadata section + # Add CycloneDX metadata section to metadata section of each software entry c_uuid, sw = convert_cyclonedx_component_to_software(component, uuids) sbom.add_software(sw) @@ -147,10 +147,9 @@ def convert_cyclonedx_component_to_software( metadata["publisher"] = component.publisher if component.group: metadata["group"] = component.group - # if component.scope: - """ Need to see some examples of this property in use - TODO: Verify that this is serializable - """ + # if component.scope: + # Need to see some examples of this property in use + # TODO: Verify that this is serializable # metadata["scope"] = component.scope # if component.licenses: # TODO: Create a proper conversion of the object into a serializable format @@ -180,66 +179,68 @@ def convert_cyclonedx_component_to_software( purl = purl + "#" + component.purl.subpath metadata["purl"] = purl - # if component.external_references: - """*** Not JSON serializable on its own despite being a serializable class. - TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["external_references"] = component.external_references - # if component.properties: - """*** Not JSON serializable on its own despite being a serializable class. - TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["properties"] = component.properties - # if component.release_notes: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ + + # if component.external_references: + # *** Not JSON serializable on its own despite being a serializable class. + # TODO: Create a proper conversion of the object into a serializable format + # metadata["external_references"] = component.external_references + + # if component.properties: + # *** Not JSON serializable on its own despite being a serializable class. + # TODO: Create a proper conversion of the object into a serializable format + # metadata["properties"] = component.properties + + # if component.release_notes: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format # metadata["release_notes"] = component.release_notes + if component.cpe: metadata["cpe"] = component.cpe - # if component.swid: - """*** Not JSON serializable on its own despite being a serializable class. - TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["swid"] = str(component.swid) - # if component.pedigree: - """*** Not JSON serializable on its own despite being a serializable class. - TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["pedigree"] = component.pedigree - # if component.evidence: - """*** Not JSON serializable on its own despite being a serializable class. - TODO: Create a proper conversion of the object into a serializable format - """ + + # if component.swid: + # *** Not JSON serializable on its own despite being a serializable class. + # TODO: Create a proper conversion of the object into a serializable format + # metadata["swid"] = str(component.swid) + + # if component.pedigree: + # *** Not JSON serializable on its own despite being a serializable class. + # TODO: Create a proper conversion of the object into a serializable format + # metadata["pedigree"] = component.pedigree + + # if component.evidence: + # *** Not JSON serializable on its own despite being a serializable class. + # TODO: Create a proper conversion of the object into a serializable format # metadata["evidence"] = component.evidence + if component.modified: metadata["modified"] = component.modified if component.manufacturer: metadata["manufacturer"] = component.manufacturer - # if component.authors: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["authors"] = component.authors - # if component.omnibor_ids: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["omnibor_ids"] = component.omnibor_ids - # if component.swhids: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["swhids"] = component.swhids - # if component.crypto_properties: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["crypto_properties"] = component.crypto_properties - # if component.tags: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ + + # if component.authors: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + # metadata["authors"] = component.authors + + # if component.omnibor_ids: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + # metadata["omnibor_ids"] = component.omnibor_ids + + # if component.swhids: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + # metadata["swhids"] = component.swhids + + # if component.crypto_properties: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + # metadata["crypto_properties"] = component.crypto_properties + + # if component.tags: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format # metadata["tags"] = component.tags # TODO: Distinguish CycloneDX files from containers From 1177c0c9cfd6d94a56d8318fbf0edadbe0c96f18 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 20:32:17 +0000 Subject: [PATCH 14/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- surfactant/input_readers/cyclonedx_reader.py | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index ad5aee48..482673d8 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -148,8 +148,8 @@ def convert_cyclonedx_component_to_software( if component.group: metadata["group"] = component.group # if component.scope: - # Need to see some examples of this property in use - # TODO: Verify that this is serializable + # Need to see some examples of this property in use + # TODO: Verify that this is serializable # metadata["scope"] = component.scope # if component.licenses: # TODO: Create a proper conversion of the object into a serializable format @@ -184,60 +184,60 @@ def convert_cyclonedx_component_to_software( # *** Not JSON serializable on its own despite being a serializable class. # TODO: Create a proper conversion of the object into a serializable format # metadata["external_references"] = component.external_references - + # if component.properties: # *** Not JSON serializable on its own despite being a serializable class. # TODO: Create a proper conversion of the object into a serializable format # metadata["properties"] = component.properties - + # if component.release_notes: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format # metadata["release_notes"] = component.release_notes - + if component.cpe: metadata["cpe"] = component.cpe - + # if component.swid: # *** Not JSON serializable on its own despite being a serializable class. # TODO: Create a proper conversion of the object into a serializable format # metadata["swid"] = str(component.swid) - + # if component.pedigree: # *** Not JSON serializable on its own despite being a serializable class. # TODO: Create a proper conversion of the object into a serializable format # metadata["pedigree"] = component.pedigree - + # if component.evidence: # *** Not JSON serializable on its own despite being a serializable class. # TODO: Create a proper conversion of the object into a serializable format # metadata["evidence"] = component.evidence - + if component.modified: metadata["modified"] = component.modified if component.manufacturer: metadata["manufacturer"] = component.manufacturer - + # if component.authors: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format # metadata["authors"] = component.authors - + # if component.omnibor_ids: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format # metadata["omnibor_ids"] = component.omnibor_ids - + # if component.swhids: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format # metadata["swhids"] = component.swhids - + # if component.crypto_properties: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format # metadata["crypto_properties"] = component.crypto_properties - + # if component.tags: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format From 07a6c6ae9494227430b77b9bd00be7d9caf1b9ae Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 15:35:21 -0500 Subject: [PATCH 15/31] pre-commit fix --- surfactant/input_readers/cyclonedx_reader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index ad5aee48..70842956 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -40,7 +40,7 @@ def read_sbom(infile) -> SBOM: for xdependency in bom.dependencies: xbomref = xdependency.ref.value - if xbomref not in uuids.keys(): + if xbomref not in uuids: new_uuid = str(uuid.uuid4()) uuids[xbomref] = new_uuid xuuid = uuids[xbomref] @@ -48,7 +48,7 @@ def read_sbom(infile) -> SBOM: for ydependency in xdependency.dependencies: ybomref = ydependency.ref.value - if ybomref not in uuids.keys(): + if ybomref not in uuids: new_uuid = str(uuid.uuid4()) uuids[ybomref] = new_uuid yuuid = uuids[ybomref] From 0fce961a7d1bc7fb3f3ab54918edb65c85c708e8 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Mon, 13 May 2024 12:08:33 -0500 Subject: [PATCH 16/31] committing current progress before rebase --- surfactant/input_readers/cyclonedx_reader.py | 59 ++++++++++++++++++++ surfactant/input_readers/spdx_reader.py | 19 +++++++ surfactant/output/cyclonedx_writer.py | 2 +- surfactant/plugin/manager.py | 7 ++- 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 surfactant/input_readers/cyclonedx_reader.py create mode 100644 surfactant/input_readers/spdx_reader.py diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py new file mode 100644 index 00000000..09f3940f --- /dev/null +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -0,0 +1,59 @@ +import pathlib +from collections.abc import Iterable +from typing import Dict, List, Optional, Tuple +import json + +import cyclonedx.output +from cyclonedx.model import HashAlgorithm, HashType, OrganizationalEntity, Tool +from cyclonedx.model.bom import Bom, BomMetaData +from cyclonedx.model.bom_ref import BomRef +from cyclonedx.model.component import Component, ComponentType +from cyclonedx.model.dependency import Dependency + +import surfactant.plugin +from surfactant import __version__ as surfactant_version +from surfactant.sbomtypes import SBOM, Software, System + +# Copyright 2024 Lawrence Livermore National Security, LLC +# See the top-level LICENSE file for details. +# +# SPDX-License-Identifier: MIT + +from typing import Optional + +import surfactant.plugin +from surfactant.sbomtypes import SBOM + + +@surfactant.plugin.hookimpl +def read_sbom(infile) -> SBOM: + bom = Bom.from_json(data=json.loads(infile.read())) + sbom = SBOM() + + # Keep track of dependecies + # bom_ref -> xuuid, dependencies -> yuuids + # Keep track of which generated UUIDs map to which bom refs + for dependency in bom.dependencies: + print(dependency) + print(dependency.dependencies) + + # Create a CyTRICS software entry for each CycloneDX component + for component in bom.components: + print(component) + # If a component detail can be mapped to a detail in a software entry, then add to software entry details + # Otherwise, add detail to software entry's metadata section + # Add CycloneDX metadata section to metadata section of each software entry? + + + + return sbom + + +@surfactant.plugin.hookimpl +def short_name() -> Optional[str]: + return "cyclonedx" + +def convert_cyclonedx_components_to_software( + component: Component, +) -> List[Tuple[str, str, Software]]: + return \ No newline at end of file diff --git a/surfactant/input_readers/spdx_reader.py b/surfactant/input_readers/spdx_reader.py new file mode 100644 index 00000000..bf5fe4f7 --- /dev/null +++ b/surfactant/input_readers/spdx_reader.py @@ -0,0 +1,19 @@ +# Copyright 2023 Lawrence Livermore National Security, LLC +# See the top-level LICENSE file for details. +# +# SPDX-License-Identifier: MIT + +from typing import Optional + +import surfactant.plugin +from surfactant.sbomtypes import SBOM + + +@surfactant.plugin.hookimpl +def read_sbom(infile) -> SBOM: + return SBOM.from_json(infile.read()) + + +@surfactant.plugin.hookimpl +def short_name() -> Optional[str]: + return "spdx" diff --git a/surfactant/output/cyclonedx_writer.py b/surfactant/output/cyclonedx_writer.py index 082c9eb4..b4a71edb 100644 --- a/surfactant/output/cyclonedx_writer.py +++ b/surfactant/output/cyclonedx_writer.py @@ -317,4 +317,4 @@ def get_software_field(software: Software, field: str): for entry in software.metadata: if "FileInfo" in entry and "LegalCopyright" in entry["FileInfo"]: return entry["FileInfo"]["LegalCopyright"] - return None + return None \ No newline at end of file diff --git a/surfactant/plugin/manager.py b/surfactant/plugin/manager.py index 58b4f07f..bcb1a29e 100644 --- a/surfactant/plugin/manager.py +++ b/surfactant/plugin/manager.py @@ -28,7 +28,10 @@ def _register_plugins(pm: pluggy.PluginManager) -> None: ole_file, pe_file, ) - from surfactant.input_readers import cytrics_reader + from surfactant.input_readers import ( + cytrics_reader, + cyclonedx_reader, + ) from surfactant.output import ( csv_writer, cyclonedx_writer, @@ -65,6 +68,8 @@ def _register_plugins(pm: pluggy.PluginManager) -> None: spdx_writer, cytrics_reader, native_lib_file, + cyclonedx_reader, + ) for plugin in internal_plugins: pm.register(plugin) From 1d3f7d3651f44322cec289cc1eaa910d8075e2a5 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Mon, 10 Jun 2024 12:33:30 -0500 Subject: [PATCH 17/31] rebase --- surfactant/input_readers/cyclonedx_reader.py | 2 +- surfactant/output/cyclonedx_writer.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 09f3940f..43358a7e 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -4,7 +4,7 @@ import json import cyclonedx.output -from cyclonedx.model import HashAlgorithm, HashType, OrganizationalEntity, Tool +from cyclonedx.model import HashAlgorithm, HashType, Tool from cyclonedx.model.bom import Bom, BomMetaData from cyclonedx.model.bom_ref import BomRef from cyclonedx.model.component import Component, ComponentType diff --git a/surfactant/output/cyclonedx_writer.py b/surfactant/output/cyclonedx_writer.py index b4a71edb..7a3c9d65 100644 --- a/surfactant/output/cyclonedx_writer.py +++ b/surfactant/output/cyclonedx_writer.py @@ -91,9 +91,9 @@ def write_sbom(sbom: SBOM, outfile) -> None: elif outformat == "xml": output_format = cyclonedx.output.OutputFormat.XML # The docs say that you don't need to specify a version (it says it defaults to the latest) - # but I got a missing keyword error when doing so, so just specify 1.5 for now + # but I got a missing keyword error when doing so, so just specify 1.6 for now outputter: cyclonedx.output.BaseOutput = cyclonedx.output.make_outputter( - bom=bom, output_format=output_format, schema_version=cyclonedx.schema.SchemaVersion.V1_5 + bom=bom, output_format=output_format, schema_version=cyclonedx.schema.SchemaVersion.V1_6 ) outfile.write(outputter.output_as_string()) From 03de3ea169891d64590946774cefd6493cdbbee2 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Mon, 17 Jun 2024 03:52:28 -0500 Subject: [PATCH 18/31] First draft of the CycloneDX reader hook --- surfactant/input_readers/cyclonedx_reader.py | 178 +++++++++++++++++-- 1 file changed, 162 insertions(+), 16 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 43358a7e..288ecae1 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -1,18 +1,14 @@ -import pathlib -from collections.abc import Iterable -from typing import Dict, List, Optional, Tuple +from typing import Dict, Optional, Tuple import json +import uuid -import cyclonedx.output -from cyclonedx.model import HashAlgorithm, HashType, Tool -from cyclonedx.model.bom import Bom, BomMetaData -from cyclonedx.model.bom_ref import BomRef -from cyclonedx.model.component import Component, ComponentType -from cyclonedx.model.dependency import Dependency +from cyclonedx.model import HashAlgorithm +from cyclonedx.model.bom import Bom +from cyclonedx.model.component import Component import surfactant.plugin from surfactant import __version__ as surfactant_version -from surfactant.sbomtypes import SBOM, Software, System +from surfactant.sbomtypes import SBOM, Software, SoftwareComponent, Relationship # Copyright 2024 Lawrence Livermore National Security, LLC # See the top-level LICENSE file for details. @@ -33,17 +29,51 @@ def read_sbom(infile) -> SBOM: # Keep track of dependecies # bom_ref -> xuuid, dependencies -> yuuids # Keep track of which generated UUIDs map to which bom refs - for dependency in bom.dependencies: - print(dependency) - print(dependency.dependencies) + uuids = {} + for xdependency in bom.dependencies: + #print(xdependency) + #print(xdependency.dependencies) + xbomref = xdependency.ref.value + if not xbomref in uuids.keys(): + new_uuid = str(uuid.uuid4()) + uuids[xbomref] = new_uuid + xuuid = uuids[xbomref] + # xuuid = xbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + + for ydependency in xdependency.dependencies: + ybomref = ydependency.ref.value + if not ybomref in uuids.keys(): + new_uuid = str(uuid.uuid4()) + uuids[ybomref] = new_uuid + yuuid = uuids[ybomref] + # yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + + # It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types + # and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being + # TODO: Add in other relationship type mappings + rel_type = "Contains" + cytrics_rel = Relationship(xUUID=xuuid,yUUID=yuuid,relationship=rel_type) + sbom.add_relationship(cytrics_rel) + + # print(sbom.relationships) # Create a CyTRICS software entry for each CycloneDX component for component in bom.components: - print(component) + # print(component) # If a component detail can be mapped to a detail in a software entry, then add to software entry details # Otherwise, add detail to software entry's metadata section # Add CycloneDX metadata section to metadata section of each software entry? + c_uuid, sw = convert_cyclonedx_components_to_software(component, uuids) + sbom.add_software(sw) + if component.bom_ref.value: + uuids[component.bom_ref.value] = c_uuid + # Do the same thing for the component from the CycloneDX metadata section (if there is one) because its bom-ref can appear in the dependencies + if bom.metadata.component: + mc_uuid, msw = convert_cyclonedx_components_to_software(bom.metadata.component, uuids) + sbom.add_software(msw) + if bom.metadata.component.bom_ref.value: + uuids[bom.metadata.component.bom_ref.value] = mc_uuid return sbom @@ -54,6 +84,122 @@ def short_name() -> Optional[str]: return "cyclonedx" def convert_cyclonedx_components_to_software( + component: Component, uuids: Dict +) -> Tuple[str, Software]: + print(component.bom_ref) + bomref = component.bom_ref.value + if (not bomref) or (not bomref in uuids.keys()): + cytrics_uuid = str(uuid.uuid4()) + else: + cytrics_uuid = uuids[bomref] + + # cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + + name = component.name + description = component.description + # CycloneDX only supports one supplier, so the vendor list will only contain one vendor + vendor = [component.supplier] + version = component.version + hashes = { + "SHA-1": None, + "SHA-256": None, + "MD5": None + } + for hash in component.hashes: + if hash.alg == HashAlgorithm.SHA_1: + hashes.update({"SHA-1": hash.content}) + elif hash.alg == HashAlgorithm.SHA_256: + hashes.update({"SHA-256": hash.content}) + elif hash.alg == HashAlgorithm.MD5: + hashes.update({"MD5": hash.content}) + + # Convert subcomponents of CycloneDX components into components of the corresponding CyTRICS software entry + sw_components = [] + for subcomp in component.components: + sw_comp = convert_cyclonedx_subcomponents_to_software_components(subcomp) + sw_components.append[sw_comp] + + # Add remaining data that is exclusive to CycloneDX component entries into the metadata section of the CyTRICS software entry + metadata = {} + if component.type: + metadata["type"] = component.type + if component.mime_type: + metadata["mime_type"] = component.mime_type + if component.publisher: + metadata["publisher"] = component.publisher + if component.group: + metadata["group"] = component.group + if component.scope: + metadata["scope"] = component.scope + if component.licenses: + metadata["licenses"] = component.licenses + if component.copyright: + metadata["copyright"] = component.copyright + if component.purl: + metadata["purl"] = component.purl + if component.external_references: + metadata["external_references"] = component.external_references + if component.properties: + metadata["properties"] = component.properties + if component.release_notes: + metadata["release_notes"] = component.release_notes + if component.cpe: + metadata["cpe"] = component.cpe + if component.swid: + metadata["swid"] = component.swid + if component.pedigree: + metadata["pedigree"] = component.pedigree + if component.evidence: + metadata["evidence"] = component.evidence + if component.modified: + metadata["modified"] = component.modified + if component.manufacturer: + metadata["manufacturer"] = component.manufacturer + if component.authors: + metadata["authors"] = component.authors + if component.omnibor_ids: + metadata["omnibor_ids"] = component.omnibor_ids + if component.swhids: + metadata["swhids"] = component.swhids + if component.crypto_properties: + metadata["crypto_properties"] = component.crypto_properties + if component.tags: + metadata["tags"] = component.tags + + # TODO: Is it possible to distinguish CycloneDX files from containers? + + sw_entry = Software( + UUID=cytrics_uuid, + name=name, + fileName="", + installPath=[""], + containerPath=[""], + version=version, + vendor=vendor, + description=description, + sha1=hashes["SHA-1"], + sha256=hashes["SHA-256"], + md5=hashes["MD5"], + metadata=metadata, + components=sw_components + ) + + return cytrics_uuid, sw_entry + +def convert_cyclonedx_subcomponents_to_software_components( component: Component, -) -> List[Tuple[str, str, Software]]: - return \ No newline at end of file +) -> SoftwareComponent: + name = component.name + description = component.description + # CycloneDX only supports one supplier, so vendor list will only contain one vendor + vendor = [component.supplier] + version = component.version + + sw_component = SoftwareComponent( + name=name, + version=version, + vendor=vendor, + description=description + ) + + return sw_component \ No newline at end of file From 6238a3f0ae0405e4f4136ea05ded780d66ae97f9 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher <55295134+mws180000@users.noreply.github.com> Date: Mon, 17 Jun 2024 03:56:21 -0500 Subject: [PATCH 19/31] Deleting old file --- surfactant/input_readers/spdx_reader.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 surfactant/input_readers/spdx_reader.py diff --git a/surfactant/input_readers/spdx_reader.py b/surfactant/input_readers/spdx_reader.py deleted file mode 100644 index bf5fe4f7..00000000 --- a/surfactant/input_readers/spdx_reader.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2023 Lawrence Livermore National Security, LLC -# See the top-level LICENSE file for details. -# -# SPDX-License-Identifier: MIT - -from typing import Optional - -import surfactant.plugin -from surfactant.sbomtypes import SBOM - - -@surfactant.plugin.hookimpl -def read_sbom(infile) -> SBOM: - return SBOM.from_json(infile.read()) - - -@surfactant.plugin.hookimpl -def short_name() -> Optional[str]: - return "spdx" From c7f4419210a9dc157797e4f4fbc454992b000aaa Mon Sep 17 00:00:00 2001 From: mws180000 Date: Tue, 18 Jun 2024 03:18:40 -0700 Subject: [PATCH 20/31] Added support for converting CycloneDX vulnerability entries into CyTRICS observations --- surfactant/input_readers/cyclonedx_reader.py | 111 +++++++++++++++---- 1 file changed, 91 insertions(+), 20 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 288ecae1..e69343cb 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -5,10 +5,11 @@ from cyclonedx.model import HashAlgorithm from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component +from cyclonedx.model.vulnerability import Vulnerability import surfactant.plugin from surfactant import __version__ as surfactant_version -from surfactant.sbomtypes import SBOM, Software, SoftwareComponent, Relationship +from surfactant.sbomtypes import SBOM, Software, SoftwareComponent, Relationship, Observation # Copyright 2024 Lawrence Livermore National Security, LLC # See the top-level LICENSE file for details. @@ -23,6 +24,18 @@ @surfactant.plugin.hookimpl def read_sbom(infile) -> SBOM: + """Reads the contents of the CycloneDX SBOM to the CyTRICS format. + + The read_sbom hook for the cyclonedx_reader makes a best-effort attempt + to map the information gathered from the CycloneDX file to a valid + internal SBOM representation. + + Args: + infile: The input file handle to read the CycloneDX SBOM from. + """ + # NOTE eventually informat should be user settable + informat = "json" + bom = Bom.from_json(data=json.loads(infile.read())) sbom = SBOM() @@ -32,14 +45,12 @@ def read_sbom(infile) -> SBOM: uuids = {} for xdependency in bom.dependencies: - #print(xdependency) - #print(xdependency.dependencies) xbomref = xdependency.ref.value if not xbomref in uuids.keys(): new_uuid = str(uuid.uuid4()) uuids[xbomref] = new_uuid xuuid = uuids[xbomref] - # xuuid = xbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + xuuid = xbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref for ydependency in xdependency.dependencies: ybomref = ydependency.ref.value @@ -47,33 +58,39 @@ def read_sbom(infile) -> SBOM: new_uuid = str(uuid.uuid4()) uuids[ybomref] = new_uuid yuuid = uuids[ybomref] - # yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref - # It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types - # and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being + """It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types + and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being""" # TODO: Add in other relationship type mappings rel_type = "Contains" cytrics_rel = Relationship(xUUID=xuuid,yUUID=yuuid,relationship=rel_type) sbom.add_relationship(cytrics_rel) - # print(sbom.relationships) # Create a CyTRICS software entry for each CycloneDX component for component in bom.components: - # print(component) - # If a component detail can be mapped to a detail in a software entry, then add to software entry details - # Otherwise, add detail to software entry's metadata section - # Add CycloneDX metadata section to metadata section of each software entry? - c_uuid, sw = convert_cyclonedx_components_to_software(component, uuids) + """If a component detail can be mapped to a detail in a software entry, then add to software entry details + Otherwise, add detail to software entry's metadata section""" + # Add CycloneDX metadata section to metadata section of each software entry + c_uuid, sw = convert_cyclonedx_component_to_software(component, uuids) sbom.add_software(sw) if component.bom_ref.value: uuids[component.bom_ref.value] = c_uuid # Do the same thing for the component from the CycloneDX metadata section (if there is one) because its bom-ref can appear in the dependencies if bom.metadata.component: - mc_uuid, msw = convert_cyclonedx_components_to_software(bom.metadata.component, uuids) + mc_uuid, msw = convert_cyclonedx_component_to_software(bom.metadata.component, uuids) sbom.add_software(msw) if bom.metadata.component.bom_ref.value: uuids[bom.metadata.component.bom_ref.value] = mc_uuid + + # Add vulnerabilities from the CycloneDX SBOM to the observations section in the CyTRICS SBOM + if bom.vulnerabilities: + for vuln in bom.vulnerabilities: + observation = convert_cyclonedx_vulnerability_to_observation(vuln) + sbom.observations.append(observation) + + return sbom @@ -83,9 +100,20 @@ def read_sbom(infile) -> SBOM: def short_name() -> Optional[str]: return "cyclonedx" -def convert_cyclonedx_components_to_software( +def convert_cyclonedx_component_to_software( component: Component, uuids: Dict ) -> Tuple[str, Software]: + """Converts a component entry in the CycloneDX SBOM to a CyTRICS software entry + + Args: + component (Component): The CycloneDX component to convert to a CyTRICS software entry. + uuids (Dict): A Python dictionary that keeps track of which CycloneDX bom-refs have already been assigned UUIDs + + Returns: + Tuple[str, Software]: A tuple containing the UUID of the Component that was + converted into a Software, and the Software object that was created. + """ + print(component.bom_ref) bomref = component.bom_ref.value if (not bomref) or (not bomref in uuids.keys()): @@ -93,10 +121,11 @@ def convert_cyclonedx_components_to_software( else: cytrics_uuid = uuids[bomref] - # cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref name = component.name description = component.description + # CycloneDX only supports one supplier, so the vendor list will only contain one vendor vendor = [component.supplier] version = component.version @@ -116,7 +145,7 @@ def convert_cyclonedx_components_to_software( # Convert subcomponents of CycloneDX components into components of the corresponding CyTRICS software entry sw_components = [] for subcomp in component.components: - sw_comp = convert_cyclonedx_subcomponents_to_software_components(subcomp) + sw_comp = convert_cyclonedx_subcomponent_to_software_components(subcomp) sw_components.append[sw_comp] # Add remaining data that is exclusive to CycloneDX component entries into the metadata section of the CyTRICS software entry @@ -186,9 +215,17 @@ def convert_cyclonedx_components_to_software( return cytrics_uuid, sw_entry -def convert_cyclonedx_subcomponents_to_software_components( - component: Component, +def convert_cyclonedx_subcomponent_to_software_components( + component: Component ) -> SoftwareComponent: + """Converts a subcomponent of a CycloneDX component into a component of the corresponding CyTRICS software entry + + Args: + component (Component): The subcomponent of the CycloneDX component to convert to a CyTRICS component in the CyTRICS software entry. + + Returns: + SoftwareComponent: The Software object that was created. + """ name = component.name description = component.description # CycloneDX only supports one supplier, so vendor list will only contain one vendor @@ -202,4 +239,38 @@ def convert_cyclonedx_subcomponents_to_software_components( description=description ) - return sw_component \ No newline at end of file + return sw_component + +def convert_cyclonedx_vulnerability_to_observation( + vulnerability: Vulnerability +) -> Observation: + """Convert a CycloneDX Vulnerability object into a CyTRICS Observation object + + Args: + vulnerability (Vulnerability): The vulnerability entry from the CycloneDX SBOM to convert to an observation entry in the CyTRICS SBOM. + + Returns: + Observation: The Observation object that was created. + """ + + vbomref = vulnerability.bom_ref.value + v_uuid = str(uuid.uuid4()) + v_uuid = vbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + cve = vulnerability.id + cvss = vulnerability.ratings.score + cwe = vulnerability.cwes + description = vulnerability.description + mitigations = vulnerability.recommendation + url = vulnerability.source.url + + sw_observation = Observation( + UUID=v_uuid, + CWEClass=cwe, + potentialEffectOrImpact=description, + CVE=cve, + CVSS=cvss, + toRecreate=url, + mitigationSuggestions=mitigations + ) + + return sw_observation \ No newline at end of file From b239b97c1fd9d6a9fb6bfb6d739ccd63404bb403 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 06:40:53 -0500 Subject: [PATCH 21/31] Made adding metadata more stable for now --- surfactant/input_readers/cyclonedx_reader.py | 75 ++++++++++---------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index e69343cb..5e66330c 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -5,7 +5,7 @@ from cyclonedx.model import HashAlgorithm from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component -from cyclonedx.model.vulnerability import Vulnerability +from cyclonedx.model.vulnerability import Vulnerability, VulnerabilityRating import surfactant.plugin from surfactant import __version__ as surfactant_version @@ -120,8 +120,7 @@ def convert_cyclonedx_component_to_software( cytrics_uuid = str(uuid.uuid4()) else: cytrics_uuid = uuids[bomref] - - cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref name = component.name description = component.description @@ -158,42 +157,42 @@ def convert_cyclonedx_component_to_software( metadata["publisher"] = component.publisher if component.group: metadata["group"] = component.group - if component.scope: - metadata["scope"] = component.scope - if component.licenses: - metadata["licenses"] = component.licenses + #if component.scope: + # metadata["scope"] = component.scope + #if component.licenses: + # metadata["licenses"] = component.licenses if component.copyright: metadata["copyright"] = component.copyright - if component.purl: - metadata["purl"] = component.purl - if component.external_references: - metadata["external_references"] = component.external_references - if component.properties: - metadata["properties"] = component.properties - if component.release_notes: - metadata["release_notes"] = component.release_notes + #if component.purl: + # metadata["purl"] = "pkg:" + component.purl.type + "/" + component.purl.namespace + "/" + component.purl.name + "@" + component.purl.version + "?" + component.purl.qualifiers + "#" + component.purl.subpath + #if component.external_references: + # metadata["external_references"] = component.external_references + #if component.properties: + # metadata["properties"] = component.properties + #if component.release_notes: + # metadata["release_notes"] = component.release_notes if component.cpe: metadata["cpe"] = component.cpe - if component.swid: - metadata["swid"] = component.swid - if component.pedigree: - metadata["pedigree"] = component.pedigree - if component.evidence: - metadata["evidence"] = component.evidence + #if component.swid: + # metadata["swid"] = component.swid + #if component.pedigree: + # metadata["pedigree"] = component.pedigree + #if component.evidence: + # metadata["evidence"] = component.evidence if component.modified: metadata["modified"] = component.modified - if component.manufacturer: - metadata["manufacturer"] = component.manufacturer - if component.authors: - metadata["authors"] = component.authors - if component.omnibor_ids: - metadata["omnibor_ids"] = component.omnibor_ids - if component.swhids: - metadata["swhids"] = component.swhids - if component.crypto_properties: - metadata["crypto_properties"] = component.crypto_properties - if component.tags: - metadata["tags"] = component.tags + #if component.manufacturer: + # metadata["manufacturer"] = component.manufacturer + #if component.authors: + # metadata["authors"] = component.authors + #if component.omnibor_ids: + # metadata["omnibor_ids"] = component.omnibor_ids + #if component.swhids: + # metadata["swhids"] = component.swhids + #if component.crypto_properties: + # metadata["crypto_properties"] = component.crypto_properties + #if component.tags: + # metadata["tags"] = component.tags # TODO: Is it possible to distinguish CycloneDX files from containers? @@ -255,13 +254,17 @@ def convert_cyclonedx_vulnerability_to_observation( vbomref = vulnerability.bom_ref.value v_uuid = str(uuid.uuid4()) - v_uuid = vbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + if vbomref: + v_uuid = vbomref # Comment this if statement if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref cve = vulnerability.id - cvss = vulnerability.ratings.score + cvss:int + for rating in vulnerability.ratings: + cvss = rating.score + break cwe = vulnerability.cwes description = vulnerability.description mitigations = vulnerability.recommendation - url = vulnerability.source.url + url = str(vulnerability.source.url) sw_observation = Observation( UUID=v_uuid, From 4f3e4d21ed7f1b8c0b79c36c9f0adbc87885a4b6 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 12:34:01 -0500 Subject: [PATCH 22/31] Updated metadata section --- surfactant/input_readers/cyclonedx_reader.py | 68 +++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 5e66330c..1c08a50a 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -4,8 +4,11 @@ from cyclonedx.model import HashAlgorithm from cyclonedx.model.bom import Bom -from cyclonedx.model.component import Component +from cyclonedx.model.component import Component, Swid, Pedigree, ComponentEvidence from cyclonedx.model.vulnerability import Vulnerability, VulnerabilityRating +from cyclonedx.model.release_note import ReleaseNotes +from cyclonedx.model.contact import OrganizationalContact, OrganizationalEntity +from cyclonedx.model.crypto import CryptoProperties import surfactant.plugin from surfactant import __version__ as surfactant_version @@ -158,43 +161,92 @@ def convert_cyclonedx_component_to_software( if component.group: metadata["group"] = component.group #if component.scope: + """ Need to see some examples of this property in use + TODO: Verify that this is serializable + """ # metadata["scope"] = component.scope #if component.licenses: + # TODO: Create a proper conversion of the object into a serializable format # metadata["licenses"] = component.licenses if component.copyright: metadata["copyright"] = component.copyright - #if component.purl: - # metadata["purl"] = "pkg:" + component.purl.type + "/" + component.purl.namespace + "/" + component.purl.name + "@" + component.purl.version + "?" + component.purl.qualifiers + "#" + component.purl.subpath + if component.purl: + purl = "pkg:" + component.purl.type + "/" + component.purl.namespace + "/" + component.purl.name + "@" + component.purl.version + if component.purl.qualifiers: + purl = purl + "?" + first = True + for qualifier in component.purl.qualifiers: + if first: + purl = purl + qualifier + "=" + component.purl.qualifiers[qualifier] + else: + purl = purl + "&" + qualifier + "=" + component.purl.qualifiers[qualifier] + if component.purl.subpath: + purl = purl + "#" + component.purl.subpath + + metadata["purl"] = purl #if component.external_references: + """*** Not JSON serializable on its own despite being a serializable class. + TODO: Create a proper conversion of the object into a serializable format + """ # metadata["external_references"] = component.external_references #if component.properties: + """*** Not JSON serializable on its own despite being a serializable class. + TODO: Create a proper conversion of the object into a serializable format + """ # metadata["properties"] = component.properties #if component.release_notes: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["release_notes"] = component.release_notes if component.cpe: metadata["cpe"] = component.cpe - #if component.swid: - # metadata["swid"] = component.swid + #if component.swid: + """*** Not JSON serializable on its own despite being a serializable class. + TODO: Create a proper conversion of the object into a serializable format + """ + # metadata["swid"] = str(component.swid) #if component.pedigree: + """*** Not JSON serializable on its own despite being a serializable class. + TODO: Create a proper conversion of the object into a serializable format + """ # metadata["pedigree"] = component.pedigree #if component.evidence: + """*** Not JSON serializable on its own despite being a serializable class. + TODO: Create a proper conversion of the object into a serializable format + """ # metadata["evidence"] = component.evidence if component.modified: metadata["modified"] = component.modified - #if component.manufacturer: - # metadata["manufacturer"] = component.manufacturer + if component.manufacturer: + metadata["manufacturer"] = component.manufacturer #if component.authors: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["authors"] = component.authors #if component.omnibor_ids: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["omnibor_ids"] = component.omnibor_ids #if component.swhids: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["swhids"] = component.swhids #if component.crypto_properties: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["crypto_properties"] = component.crypto_properties #if component.tags: + """ Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + """ # metadata["tags"] = component.tags - # TODO: Is it possible to distinguish CycloneDX files from containers? + # TODO: Distinguish CycloneDX files from containers sw_entry = Software( UUID=cytrics_uuid, From b8ae21f4327ffe8e1e69551b1b09481c03075297 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 13:06:17 -0500 Subject: [PATCH 23/31] pre-commit fixes --- surfactant/input_readers/cyclonedx_reader.py | 25 +++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 1c08a50a..949d7188 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -1,14 +1,11 @@ -from typing import Dict, Optional, Tuple +from typing import Dict, Optional, Tuple, List import json import uuid from cyclonedx.model import HashAlgorithm from cyclonedx.model.bom import Bom -from cyclonedx.model.component import Component, Swid, Pedigree, ComponentEvidence -from cyclonedx.model.vulnerability import Vulnerability, VulnerabilityRating -from cyclonedx.model.release_note import ReleaseNotes -from cyclonedx.model.contact import OrganizationalContact, OrganizationalEntity -from cyclonedx.model.crypto import CryptoProperties +from cyclonedx.model.component import Component +from cyclonedx.model.vulnerability import Vulnerability import surfactant.plugin from surfactant import __version__ as surfactant_version @@ -136,16 +133,16 @@ def convert_cyclonedx_component_to_software( "SHA-256": None, "MD5": None } - for hash in component.hashes: - if hash.alg == HashAlgorithm.SHA_1: - hashes.update({"SHA-1": hash.content}) - elif hash.alg == HashAlgorithm.SHA_256: - hashes.update({"SHA-256": hash.content}) - elif hash.alg == HashAlgorithm.MD5: - hashes.update({"MD5": hash.content}) + for c_hash in component.hashes: + if c_hash.alg == HashAlgorithm.SHA_1: + hashes.update({"SHA-1": c_hash.content}) + elif c_hash.alg == HashAlgorithm.SHA_256: + hashes.update({"SHA-256": c_hash.content}) + elif c_hash.alg == HashAlgorithm.MD5: + hashes.update({"MD5": c_hash.content}) # Convert subcomponents of CycloneDX components into components of the corresponding CyTRICS software entry - sw_components = [] + sw_components: List[SoftwareComponent] = [] for subcomp in component.components: sw_comp = convert_cyclonedx_subcomponent_to_software_components(subcomp) sw_components.append[sw_comp] From 8edd714a7bbb599093000d7db6395c6d4cc2fd13 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:49:10 +0000 Subject: [PATCH 24/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- surfactant/input_readers/cyclonedx_reader.py | 158 ++++++++++--------- surfactant/output/cyclonedx_writer.py | 2 +- surfactant/plugin/manager.py | 4 +- 3 files changed, 84 insertions(+), 80 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 949d7188..7ad9c86a 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -15,19 +15,23 @@ # See the top-level LICENSE file for details. # # SPDX-License-Identifier: MIT +from typing import Dict, Optional, Tuple -from typing import Optional +from cyclonedx.model import HashAlgorithm +from cyclonedx.model.bom import Bom +from cyclonedx.model.component import Component +from cyclonedx.model.vulnerability import Vulnerability import surfactant.plugin -from surfactant.sbomtypes import SBOM +from surfactant.sbomtypes import SBOM, Observation, Relationship, Software, SoftwareComponent @surfactant.plugin.hookimpl -def read_sbom(infile) -> SBOM: +def read_sbom(infile) -> SBOM: """Reads the contents of the CycloneDX SBOM to the CyTRICS format. The read_sbom hook for the cyclonedx_reader makes a best-effort attempt - to map the information gathered from the CycloneDX file to a valid + to map the information gathered from the CycloneDX file to a valid internal SBOM representation. Args: @@ -35,36 +39,36 @@ def read_sbom(infile) -> SBOM: """ # NOTE eventually informat should be user settable informat = "json" - + bom = Bom.from_json(data=json.loads(infile.read())) sbom = SBOM() - + # Keep track of dependecies # bom_ref -> xuuid, dependencies -> yuuids - # Keep track of which generated UUIDs map to which bom refs + # Keep track of which generated UUIDs map to which bom refs uuids = {} for xdependency in bom.dependencies: xbomref = xdependency.ref.value - if not xbomref in uuids.keys(): + if xbomref not in uuids.keys(): new_uuid = str(uuid.uuid4()) uuids[xbomref] = new_uuid xuuid = uuids[xbomref] - xuuid = xbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref - + xuuid = xbomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + for ydependency in xdependency.dependencies: ybomref = ydependency.ref.value - if not ybomref in uuids.keys(): + if ybomref not in uuids.keys(): new_uuid = str(uuid.uuid4()) uuids[ybomref] = new_uuid yuuid = uuids[ybomref] - yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref """It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being""" # TODO: Add in other relationship type mappings rel_type = "Contains" - cytrics_rel = Relationship(xUUID=xuuid,yUUID=yuuid,relationship=rel_type) + cytrics_rel = Relationship(xUUID=xuuid, yUUID=yuuid, relationship=rel_type) sbom.add_relationship(cytrics_rel) # Create a CyTRICS software entry for each CycloneDX component @@ -76,23 +80,20 @@ def read_sbom(infile) -> SBOM: sbom.add_software(sw) if component.bom_ref.value: uuids[component.bom_ref.value] = c_uuid - + # Do the same thing for the component from the CycloneDX metadata section (if there is one) because its bom-ref can appear in the dependencies if bom.metadata.component: mc_uuid, msw = convert_cyclonedx_component_to_software(bom.metadata.component, uuids) sbom.add_software(msw) if bom.metadata.component.bom_ref.value: uuids[bom.metadata.component.bom_ref.value] = mc_uuid - + # Add vulnerabilities from the CycloneDX SBOM to the observations section in the CyTRICS SBOM if bom.vulnerabilities: for vuln in bom.vulnerabilities: observation = convert_cyclonedx_vulnerability_to_observation(vuln) sbom.observations.append(observation) - - - return sbom @@ -100,6 +101,7 @@ def read_sbom(infile) -> SBOM: def short_name() -> Optional[str]: return "cyclonedx" + def convert_cyclonedx_component_to_software( component: Component, uuids: Dict ) -> Tuple[str, Software]: @@ -116,37 +118,33 @@ def convert_cyclonedx_component_to_software( print(component.bom_ref) bomref = component.bom_ref.value - if (not bomref) or (not bomref in uuids.keys()): + if (not bomref) or (bomref not in uuids.keys()): cytrics_uuid = str(uuid.uuid4()) else: cytrics_uuid = uuids[bomref] - cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + cytrics_uuid = bomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref name = component.name description = component.description - + # CycloneDX only supports one supplier, so the vendor list will only contain one vendor vendor = [component.supplier] version = component.version - hashes = { - "SHA-1": None, - "SHA-256": None, - "MD5": None - } - for c_hash in component.hashes: - if c_hash.alg == HashAlgorithm.SHA_1: - hashes.update({"SHA-1": c_hash.content}) - elif c_hash.alg == HashAlgorithm.SHA_256: - hashes.update({"SHA-256": c_hash.content}) - elif c_hash.alg == HashAlgorithm.MD5: - hashes.update({"MD5": c_hash.content}) - + hashes = {"SHA-1": None, "SHA-256": None, "MD5": None} + for hash in component.hashes: + if hash.alg == HashAlgorithm.SHA_1: + hashes.update({"SHA-1": hash.content}) + elif hash.alg == HashAlgorithm.SHA_256: + hashes.update({"SHA-256": hash.content}) + elif hash.alg == HashAlgorithm.MD5: + hashes.update({"MD5": hash.content}) + # Convert subcomponents of CycloneDX components into components of the corresponding CyTRICS software entry sw_components: List[SoftwareComponent] = [] for subcomp in component.components: sw_comp = convert_cyclonedx_subcomponent_to_software_components(subcomp) sw_components.append[sw_comp] - + # Add remaining data that is exclusive to CycloneDX component entries into the metadata section of the CyTRICS software entry metadata = {} if component.type: @@ -157,18 +155,27 @@ def convert_cyclonedx_component_to_software( metadata["publisher"] = component.publisher if component.group: metadata["group"] = component.group - #if component.scope: + # if component.scope: """ Need to see some examples of this property in use TODO: Verify that this is serializable """ # metadata["scope"] = component.scope - #if component.licenses: - # TODO: Create a proper conversion of the object into a serializable format + # if component.licenses: + # TODO: Create a proper conversion of the object into a serializable format # metadata["licenses"] = component.licenses if component.copyright: metadata["copyright"] = component.copyright if component.purl: - purl = "pkg:" + component.purl.type + "/" + component.purl.namespace + "/" + component.purl.name + "@" + component.purl.version + purl = ( + "pkg:" + + component.purl.type + + "/" + + component.purl.namespace + + "/" + + component.purl.name + + "@" + + component.purl.version + ) if component.purl.qualifiers: purl = purl + "?" first = True @@ -181,35 +188,35 @@ def convert_cyclonedx_component_to_software( purl = purl + "#" + component.purl.subpath metadata["purl"] = purl - #if component.external_references: - """*** Not JSON serializable on its own despite being a serializable class. + # if component.external_references: + """*** Not JSON serializable on its own despite being a serializable class. TODO: Create a proper conversion of the object into a serializable format """ - # metadata["external_references"] = component.external_references - #if component.properties: - """*** Not JSON serializable on its own despite being a serializable class. + # metadata["external_references"] = component.external_references + # if component.properties: + """*** Not JSON serializable on its own despite being a serializable class. TODO: Create a proper conversion of the object into a serializable format """ - # metadata["properties"] = component.properties - #if component.release_notes: + # metadata["properties"] = component.properties + # if component.release_notes: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ # metadata["release_notes"] = component.release_notes if component.cpe: metadata["cpe"] = component.cpe - #if component.swid: - """*** Not JSON serializable on its own despite being a serializable class. + # if component.swid: + """*** Not JSON serializable on its own despite being a serializable class. TODO: Create a proper conversion of the object into a serializable format """ - # metadata["swid"] = str(component.swid) - #if component.pedigree: - """*** Not JSON serializable on its own despite being a serializable class. + # metadata["swid"] = str(component.swid) + # if component.pedigree: + """*** Not JSON serializable on its own despite being a serializable class. TODO: Create a proper conversion of the object into a serializable format """ - # metadata["pedigree"] = component.pedigree - #if component.evidence: - """*** Not JSON serializable on its own despite being a serializable class. + # metadata["pedigree"] = component.pedigree + # if component.evidence: + """*** Not JSON serializable on its own despite being a serializable class. TODO: Create a proper conversion of the object into a serializable format """ # metadata["evidence"] = component.evidence @@ -217,27 +224,27 @@ def convert_cyclonedx_component_to_software( metadata["modified"] = component.modified if component.manufacturer: metadata["manufacturer"] = component.manufacturer - #if component.authors: + # if component.authors: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ - # metadata["authors"] = component.authors - #if component.omnibor_ids: + # metadata["authors"] = component.authors + # if component.omnibor_ids: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ - # metadata["omnibor_ids"] = component.omnibor_ids - #if component.swhids: + # metadata["omnibor_ids"] = component.omnibor_ids + # if component.swhids: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ - # metadata["swhids"] = component.swhids - #if component.crypto_properties: + # metadata["swhids"] = component.swhids + # if component.crypto_properties: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ - # metadata["crypto_properties"] = component.crypto_properties - #if component.tags: + # metadata["crypto_properties"] = component.crypto_properties + # if component.tags: """ Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format """ @@ -258,13 +265,14 @@ def convert_cyclonedx_component_to_software( sha256=hashes["SHA-256"], md5=hashes["MD5"], metadata=metadata, - components=sw_components + components=sw_components, ) return cytrics_uuid, sw_entry + def convert_cyclonedx_subcomponent_to_software_components( - component: Component + component: Component, ) -> SoftwareComponent: """Converts a subcomponent of a CycloneDX component into a component of the corresponding CyTRICS software entry @@ -281,17 +289,13 @@ def convert_cyclonedx_subcomponent_to_software_components( version = component.version sw_component = SoftwareComponent( - name=name, - version=version, - vendor=vendor, - description=description + name=name, version=version, vendor=vendor, description=description ) return sw_component -def convert_cyclonedx_vulnerability_to_observation( - vulnerability: Vulnerability -) -> Observation: + +def convert_cyclonedx_vulnerability_to_observation(vulnerability: Vulnerability) -> Observation: """Convert a CycloneDX Vulnerability object into a CyTRICS Observation object Args: @@ -300,13 +304,13 @@ def convert_cyclonedx_vulnerability_to_observation( Returns: Observation: The Observation object that was created. """ - + vbomref = vulnerability.bom_ref.value v_uuid = str(uuid.uuid4()) if vbomref: - v_uuid = vbomref # Comment this if statement if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref + v_uuid = vbomref # Comment this if statement if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref cve = vulnerability.id - cvss:int + cvss: int for rating in vulnerability.ratings: cvss = rating.score break @@ -322,7 +326,7 @@ def convert_cyclonedx_vulnerability_to_observation( CVE=cve, CVSS=cvss, toRecreate=url, - mitigationSuggestions=mitigations + mitigationSuggestions=mitigations, ) - return sw_observation \ No newline at end of file + return sw_observation diff --git a/surfactant/output/cyclonedx_writer.py b/surfactant/output/cyclonedx_writer.py index 7a3c9d65..5536482f 100644 --- a/surfactant/output/cyclonedx_writer.py +++ b/surfactant/output/cyclonedx_writer.py @@ -317,4 +317,4 @@ def get_software_field(software: Software, field: str): for entry in software.metadata: if "FileInfo" in entry and "LegalCopyright" in entry["FileInfo"]: return entry["FileInfo"]["LegalCopyright"] - return None \ No newline at end of file + return None diff --git a/surfactant/plugin/manager.py b/surfactant/plugin/manager.py index bcb1a29e..e4854485 100644 --- a/surfactant/plugin/manager.py +++ b/surfactant/plugin/manager.py @@ -29,9 +29,9 @@ def _register_plugins(pm: pluggy.PluginManager) -> None: pe_file, ) from surfactant.input_readers import ( - cytrics_reader, cyclonedx_reader, - ) + cytrics_reader, + ) from surfactant.output import ( csv_writer, cyclonedx_writer, From 5266565cb09b74f1ba2d7668f1222235f5c4a212 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 13:24:42 -0500 Subject: [PATCH 25/31] pre-commit fix --- surfactant/input_readers/cyclonedx_reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 7ad9c86a..8722799b 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -143,7 +143,7 @@ def convert_cyclonedx_component_to_software( sw_components: List[SoftwareComponent] = [] for subcomp in component.components: sw_comp = convert_cyclonedx_subcomponent_to_software_components(subcomp) - sw_components.append[sw_comp] + sw_components.append(sw_comp) # Add remaining data that is exclusive to CycloneDX component entries into the metadata section of the CyTRICS software entry metadata = {} From ff03a32738ddc7bcf1a70276586c3fd89d3fc38f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:11:47 +0000 Subject: [PATCH 26/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- surfactant/input_readers/cyclonedx_reader.py | 26 ++++++-------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 8722799b..31e91fc4 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -1,21 +1,11 @@ -from typing import Dict, Optional, Tuple, List import json import uuid -from cyclonedx.model import HashAlgorithm -from cyclonedx.model.bom import Bom -from cyclonedx.model.component import Component -from cyclonedx.model.vulnerability import Vulnerability - -import surfactant.plugin -from surfactant import __version__ as surfactant_version -from surfactant.sbomtypes import SBOM, Software, SoftwareComponent, Relationship, Observation - # Copyright 2024 Lawrence Livermore National Security, LLC # See the top-level LICENSE file for details. # # SPDX-License-Identifier: MIT -from typing import Dict, Optional, Tuple +from typing import Dict, List, Optional, Tuple from cyclonedx.model import HashAlgorithm from cyclonedx.model.bom import Bom @@ -131,13 +121,13 @@ def convert_cyclonedx_component_to_software( vendor = [component.supplier] version = component.version hashes = {"SHA-1": None, "SHA-256": None, "MD5": None} - for hash in component.hashes: - if hash.alg == HashAlgorithm.SHA_1: - hashes.update({"SHA-1": hash.content}) - elif hash.alg == HashAlgorithm.SHA_256: - hashes.update({"SHA-256": hash.content}) - elif hash.alg == HashAlgorithm.MD5: - hashes.update({"MD5": hash.content}) + for c_hash in component.hashes: + if c_hash.alg == HashAlgorithm.SHA_1: + hashes.update({"SHA-1": c_hash.content}) + elif c_hash.alg == HashAlgorithm.SHA_256: + hashes.update({"SHA-256": c_hash.content}) + elif c_hash.alg == HashAlgorithm.MD5: + hashes.update({"MD5": c_hash.content}) # Convert subcomponents of CycloneDX components into components of the corresponding CyTRICS software entry sw_components: List[SoftwareComponent] = [] From 92455bebee57a37bc8c53fdf4637efe1fd54bc76 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 13:34:06 -0500 Subject: [PATCH 27/31] precommit-fix --- surfactant/input_readers/cyclonedx_reader.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 31e91fc4..71f782d4 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -55,7 +55,8 @@ def read_sbom(infile) -> SBOM: yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref """It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types - and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being""" + and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being + """ # TODO: Add in other relationship type mappings rel_type = "Contains" cytrics_rel = Relationship(xUUID=xuuid, yUUID=yuuid, relationship=rel_type) @@ -64,7 +65,8 @@ def read_sbom(infile) -> SBOM: # Create a CyTRICS software entry for each CycloneDX component for component in bom.components: """If a component detail can be mapped to a detail in a software entry, then add to software entry details - Otherwise, add detail to software entry's metadata section""" + Otherwise, add detail to software entry's metadata section + """ # Add CycloneDX metadata section to metadata section of each software entry c_uuid, sw = convert_cyclonedx_component_to_software(component, uuids) sbom.add_software(sw) From 97d1fc24c83d6c3e24737c4ae3c6c7906e778dc0 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 15:32:00 -0500 Subject: [PATCH 28/31] pre-commit fixes --- surfactant/input_readers/cyclonedx_reader.py | 125 ++++++++++--------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 71f782d4..ad5aee48 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -54,9 +54,9 @@ def read_sbom(infile) -> SBOM: yuuid = uuids[ybomref] yuuid = ybomref # Comment this line if you want the uuid to look like the CyTRICS uuid, uncomment if you want the uuid to match the bom-ref - """It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types - and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being - """ + # It is unclear what different CycloneDX dependency types exist outside of the type shown in the official examples of CycloneDX SBOM types + # and how those would map to CyTRICS's relationship types, so each relationship between CycloneDX components will be labeled as "Contains" for the time being + # TODO: Add in other relationship type mappings rel_type = "Contains" cytrics_rel = Relationship(xUUID=xuuid, yUUID=yuuid, relationship=rel_type) @@ -64,9 +64,9 @@ def read_sbom(infile) -> SBOM: # Create a CyTRICS software entry for each CycloneDX component for component in bom.components: - """If a component detail can be mapped to a detail in a software entry, then add to software entry details - Otherwise, add detail to software entry's metadata section - """ + # If a component detail can be mapped to a detail in a software entry, then add to software entry details + # Otherwise, add detail to software entry's metadata section + # Add CycloneDX metadata section to metadata section of each software entry c_uuid, sw = convert_cyclonedx_component_to_software(component, uuids) sbom.add_software(sw) @@ -147,10 +147,9 @@ def convert_cyclonedx_component_to_software( metadata["publisher"] = component.publisher if component.group: metadata["group"] = component.group - # if component.scope: - """ Need to see some examples of this property in use - TODO: Verify that this is serializable - """ + # if component.scope: + # Need to see some examples of this property in use + # TODO: Verify that this is serializable # metadata["scope"] = component.scope # if component.licenses: # TODO: Create a proper conversion of the object into a serializable format @@ -180,66 +179,68 @@ def convert_cyclonedx_component_to_software( purl = purl + "#" + component.purl.subpath metadata["purl"] = purl - # if component.external_references: - """*** Not JSON serializable on its own despite being a serializable class. - TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["external_references"] = component.external_references - # if component.properties: - """*** Not JSON serializable on its own despite being a serializable class. - TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["properties"] = component.properties - # if component.release_notes: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ + + # if component.external_references: + # *** Not JSON serializable on its own despite being a serializable class. + # TODO: Create a proper conversion of the object into a serializable format + # metadata["external_references"] = component.external_references + + # if component.properties: + # *** Not JSON serializable on its own despite being a serializable class. + # TODO: Create a proper conversion of the object into a serializable format + # metadata["properties"] = component.properties + + # if component.release_notes: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format # metadata["release_notes"] = component.release_notes + if component.cpe: metadata["cpe"] = component.cpe - # if component.swid: - """*** Not JSON serializable on its own despite being a serializable class. - TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["swid"] = str(component.swid) - # if component.pedigree: - """*** Not JSON serializable on its own despite being a serializable class. - TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["pedigree"] = component.pedigree - # if component.evidence: - """*** Not JSON serializable on its own despite being a serializable class. - TODO: Create a proper conversion of the object into a serializable format - """ + + # if component.swid: + # *** Not JSON serializable on its own despite being a serializable class. + # TODO: Create a proper conversion of the object into a serializable format + # metadata["swid"] = str(component.swid) + + # if component.pedigree: + # *** Not JSON serializable on its own despite being a serializable class. + # TODO: Create a proper conversion of the object into a serializable format + # metadata["pedigree"] = component.pedigree + + # if component.evidence: + # *** Not JSON serializable on its own despite being a serializable class. + # TODO: Create a proper conversion of the object into a serializable format # metadata["evidence"] = component.evidence + if component.modified: metadata["modified"] = component.modified if component.manufacturer: metadata["manufacturer"] = component.manufacturer - # if component.authors: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["authors"] = component.authors - # if component.omnibor_ids: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["omnibor_ids"] = component.omnibor_ids - # if component.swhids: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["swhids"] = component.swhids - # if component.crypto_properties: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ - # metadata["crypto_properties"] = component.crypto_properties - # if component.tags: - """ Need to see some examples of this property in use - # TODO: Create a proper conversion of the object into a serializable format - """ + + # if component.authors: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + # metadata["authors"] = component.authors + + # if component.omnibor_ids: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + # metadata["omnibor_ids"] = component.omnibor_ids + + # if component.swhids: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + # metadata["swhids"] = component.swhids + + # if component.crypto_properties: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format + # metadata["crypto_properties"] = component.crypto_properties + + # if component.tags: + # Need to see some examples of this property in use + # TODO: Create a proper conversion of the object into a serializable format # metadata["tags"] = component.tags # TODO: Distinguish CycloneDX files from containers From 7e11df3f942bfaadadc7348452e403812968ca63 Mon Sep 17 00:00:00 2001 From: Marlon Schumacher Date: Tue, 18 Jun 2024 15:35:21 -0500 Subject: [PATCH 29/31] pre-commit fix --- surfactant/input_readers/cyclonedx_reader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index ad5aee48..70842956 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -40,7 +40,7 @@ def read_sbom(infile) -> SBOM: for xdependency in bom.dependencies: xbomref = xdependency.ref.value - if xbomref not in uuids.keys(): + if xbomref not in uuids: new_uuid = str(uuid.uuid4()) uuids[xbomref] = new_uuid xuuid = uuids[xbomref] @@ -48,7 +48,7 @@ def read_sbom(infile) -> SBOM: for ydependency in xdependency.dependencies: ybomref = ydependency.ref.value - if ybomref not in uuids.keys(): + if ybomref not in uuids: new_uuid = str(uuid.uuid4()) uuids[ybomref] = new_uuid yuuid = uuids[ybomref] From 0a734f69823297314c4a580b64707ee53820e467 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 20:32:17 +0000 Subject: [PATCH 30/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- surfactant/input_readers/cyclonedx_reader.py | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/surfactant/input_readers/cyclonedx_reader.py b/surfactant/input_readers/cyclonedx_reader.py index 70842956..0b683b51 100644 --- a/surfactant/input_readers/cyclonedx_reader.py +++ b/surfactant/input_readers/cyclonedx_reader.py @@ -148,8 +148,8 @@ def convert_cyclonedx_component_to_software( if component.group: metadata["group"] = component.group # if component.scope: - # Need to see some examples of this property in use - # TODO: Verify that this is serializable + # Need to see some examples of this property in use + # TODO: Verify that this is serializable # metadata["scope"] = component.scope # if component.licenses: # TODO: Create a proper conversion of the object into a serializable format @@ -184,60 +184,60 @@ def convert_cyclonedx_component_to_software( # *** Not JSON serializable on its own despite being a serializable class. # TODO: Create a proper conversion of the object into a serializable format # metadata["external_references"] = component.external_references - + # if component.properties: # *** Not JSON serializable on its own despite being a serializable class. # TODO: Create a proper conversion of the object into a serializable format # metadata["properties"] = component.properties - + # if component.release_notes: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format # metadata["release_notes"] = component.release_notes - + if component.cpe: metadata["cpe"] = component.cpe - + # if component.swid: # *** Not JSON serializable on its own despite being a serializable class. # TODO: Create a proper conversion of the object into a serializable format # metadata["swid"] = str(component.swid) - + # if component.pedigree: # *** Not JSON serializable on its own despite being a serializable class. # TODO: Create a proper conversion of the object into a serializable format # metadata["pedigree"] = component.pedigree - + # if component.evidence: # *** Not JSON serializable on its own despite being a serializable class. # TODO: Create a proper conversion of the object into a serializable format # metadata["evidence"] = component.evidence - + if component.modified: metadata["modified"] = component.modified if component.manufacturer: metadata["manufacturer"] = component.manufacturer - + # if component.authors: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format # metadata["authors"] = component.authors - + # if component.omnibor_ids: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format # metadata["omnibor_ids"] = component.omnibor_ids - + # if component.swhids: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format # metadata["swhids"] = component.swhids - + # if component.crypto_properties: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format # metadata["crypto_properties"] = component.crypto_properties - + # if component.tags: # Need to see some examples of this property in use # TODO: Create a proper conversion of the object into a serializable format From 4832ea2cfd7a8e0c641f9333cbf93b06cb0e46fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 21:58:32 +0000 Subject: [PATCH 31/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- surfactant/plugin/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/surfactant/plugin/manager.py b/surfactant/plugin/manager.py index e4854485..1c4ebd8e 100644 --- a/surfactant/plugin/manager.py +++ b/surfactant/plugin/manager.py @@ -69,7 +69,6 @@ def _register_plugins(pm: pluggy.PluginManager) -> None: cytrics_reader, native_lib_file, cyclonedx_reader, - ) for plugin in internal_plugins: pm.register(plugin)