Coverage for /home/benjarobin/Bootlin/projects/Schneider-Electric-Senux/sbom-cve-check/src/sbom_cve_check/sbom/sbom_spdx3.py: 83%
110 statements
« prev ^ index » next coverage.py v7.11.1, created at 2025-11-28 15:37 +0100
« prev ^ index » next coverage.py v7.11.1, created at 2025-11-28 15:37 +0100
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: GPL-2.0-only
3"""
4Implements SBOMs in the SPDX3 format.
6SPDX3 is an open standard for Bill Of Materials, whose specification can be
7found at https://spdx.github.io/spdx-spec/v3.0.1/front/introduction/.
8"""
10import datetime
11import pathlib
12from collections.abc import Generator, Iterable
13from typing import Any
15from spdx_python_model.bindings import v3_0_1 as spdx30 # type: ignore[import-untyped]
17from ..cve_db.annot_spdx3 import Spdx3AnnotDatabase
18from ..vuln.cpe import Cpe23
19from ..vuln.cve import CveId, CveInfo
20from .component import CompBuild, CompId, Component, CompType, CompVersIds
21from .lib_spdx3 import ObjectSet
22from .registry import register_sbom
23from .sbom_base import Sbom
26class Spdx3Component(Component):
27 def __init__(self, objset: ObjectSet, pkg: spdx30.software_Package) -> None:
28 self._objset = objset
29 self._pkg = pkg
31 @property
32 def spdx_package(self) -> spdx30.software_Package:
33 return self._pkg
35 @property
36 def name(self) -> str:
37 return str(self._pkg.name)
39 @property
40 def version(self) -> str | None:
41 for cpe in ObjectSet.list_cpe23(self._pkg):
42 if isinstance(cpe.version, str):
43 return cpe.version
44 v = self._pkg.software_packageVersion
45 return str(v) if v else None
47 @property
48 def supplier(self) -> str | None:
49 for created_by in self._pkg.creationInfo.createdBy:
50 if created_by.name:
51 return str(created_by.name)
52 return None
54 @property
55 def comp_type(self) -> CompType | None:
56 for cpe in ObjectSet.list_cpe23(self._pkg):
57 if cpe.part == "o":
58 return CompType.OS
59 if cpe.part == "a":
60 return CompType.APPLICATION
61 return None
63 @property
64 def identifiers(self) -> set[CompId]:
65 comp_ids: set[CompId] = set()
66 for cpe in ObjectSet.list_cpe23(self._pkg):
67 comp_id = CompId.build_from_cpe(cpe)
68 if comp_id is not None:
69 comp_ids.add(comp_id)
70 return comp_ids
72 @property
73 def cpes(self) -> set[Cpe23]:
74 return ObjectSet.list_cpe23(self._pkg)
76 @property
77 def description(self) -> str | None:
78 d = self._pkg.description
79 return d if isinstance(d, str) else None
81 def add_cve_vulnerability(
82 self, cve_info: CveInfo, *, update_vuln_info: bool = True
83 ) -> None:
84 self._objset.add_cve_vulnerability(
85 self._pkg, cve_info, update_vuln_info=update_vuln_info
86 )
89class Spdx3CompBuild(CompBuild):
90 def __init__(
91 self,
92 vers_ids: CompVersIds,
93 comps: Iterable[Component],
94 objset: ObjectSet,
95 build_obj: spdx30.build_Build,
96 ) -> None:
97 super().__init__(vers_ids, comps)
98 self._objset = objset
99 self._build = build_obj
101 @property
102 def build_name(self) -> str | None:
103 name: str | None = self._build.name
104 if (
105 name
106 and self._build.build_buildType
107 and self._build.build_buildType.startswith(
108 "http://openembedded.org/bitbake"
109 )
110 ):
111 return name.split(":", 1)[0]
112 return name
114 def _get_compiled_sources(self) -> set[str]:
115 sources: set[str] = set()
116 for artifact in self._objset.get_build_recipe_sources(self._build):
117 if isinstance(artifact, spdx30.software_File):
118 path: str = artifact.name
119 if path:
120 sources.add(path)
121 return sources
124@register_sbom("spdx3")
125class Spdx3Sbom(Sbom):
126 @classmethod
127 def can_handle_sbom(cls, path: pathlib.Path) -> bool:
128 return path.name.endswith(".spdx.json")
130 def __init__(self, path: pathlib.Path) -> None:
131 super().__init__(path)
132 self._objset: ObjectSet = ObjectSet.parse_jsonld(path)
134 def create_annot_database(self, **kwargs: Any) -> Spdx3AnnotDatabase | None:
135 return Spdx3AnnotDatabase(path=None, objset=self._objset, **kwargs)
137 def iterate_component_builds(self) -> Generator[CompBuild, None, None]:
138 for build_obj in sorted(self._objset.iterate_builds(), key=lambda b: b.name):
139 comps = (
140 Spdx3Component(self._objset, pkg)
141 for pkg in self._objset.get_build_recipe_packages(build_obj)
142 )
143 for vers_id, group in CompBuild.group_components_by_id(comps).items():
144 yield Spdx3CompBuild(vers_id, group, self._objset, build_obj)
146 @property
147 def supplier(self) -> str | None:
148 return None
150 @property
151 def timestamp(self) -> datetime.datetime | None:
152 return None
154 def update_sbom_generation_tools(self) -> None:
155 tool_name = "sbom-cve-check"
156 agent = self._objset.new_agent(
157 pn=tool_name, agent_type="software", name=tool_name
158 )
159 tool = self._objset.new_tool(pn=tool_name, name=tool_name)
160 creation_info = self._objset.new_creation_info(tools=[tool], agents=[agent])
161 self._objset.set_doc_creation_info(creation_info)
163 def write_to_file(self, path_sbom: str | pathlib.Path) -> None:
164 self._objset.write_to_jsonld(path_sbom)
166 def remove_all_cve_vulnerability(self, cve_id: CveId) -> None:
167 self._objset.remove_all_cve_vulnerability(cve_id.id)