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

1# -*- coding: utf-8 -*- 

2# SPDX-License-Identifier: GPL-2.0-only 

3""" 

4Implements SBOMs in the SPDX3 format. 

5 

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""" 

9 

10import datetime 

11import pathlib 

12from collections.abc import Generator, Iterable 

13from typing import Any 

14 

15from spdx_python_model.bindings import v3_0_1 as spdx30 # type: ignore[import-untyped] 

16 

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 

24 

25 

26class Spdx3Component(Component): 

27 def __init__(self, objset: ObjectSet, pkg: spdx30.software_Package) -> None: 

28 self._objset = objset 

29 self._pkg = pkg 

30 

31 @property 

32 def spdx_package(self) -> spdx30.software_Package: 

33 return self._pkg 

34 

35 @property 

36 def name(self) -> str: 

37 return str(self._pkg.name) 

38 

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 

46 

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 

53 

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 

62 

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 

71 

72 @property 

73 def cpes(self) -> set[Cpe23]: 

74 return ObjectSet.list_cpe23(self._pkg) 

75 

76 @property 

77 def description(self) -> str | None: 

78 d = self._pkg.description 

79 return d if isinstance(d, str) else None 

80 

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 ) 

87 

88 

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 

100 

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 

113 

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 

122 

123 

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") 

129 

130 def __init__(self, path: pathlib.Path) -> None: 

131 super().__init__(path) 

132 self._objset: ObjectSet = ObjectSet.parse_jsonld(path) 

133 

134 def create_annot_database(self, **kwargs: Any) -> Spdx3AnnotDatabase | None: 

135 return Spdx3AnnotDatabase(path=None, objset=self._objset, **kwargs) 

136 

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) 

145 

146 @property 

147 def supplier(self) -> str | None: 

148 return None 

149 

150 @property 

151 def timestamp(self) -> datetime.datetime | None: 

152 return None 

153 

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) 

162 

163 def write_to_file(self, path_sbom: str | pathlib.Path) -> None: 

164 self._objset.write_to_jsonld(path_sbom) 

165 

166 def remove_all_cve_vulnerability(self, cve_id: CveId) -> None: 

167 self._objset.remove_all_cve_vulnerability(cve_id.id)