Coverage for /home/benjarobin/Bootlin/projects/Schneider-Electric-Senux/sbom-cve-check/src/sbom_cve_check/cve_db/annot_spdx3.py: 95%

94 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 

4import pathlib 

5from collections.abc import Generator, Iterable 

6from datetime import datetime 

7from typing import Any 

8 

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

10 

11from ..sbom import lib_spdx3 

12from ..sbom.component import CompId 

13from ..vuln.cpe import Cpe23 

14from ..vuln.cve import ( 

15 CveExtReference, 

16 CveId, 

17 CveVexAssessment, 

18 find_most_appropriate_assessment, 

19) 

20from ..vuln.cvss import CvssMetric 

21from ..vuln.version import SemVerRange 

22from .annot_base import AnnotDatabase, AnnotDbEntry 

23from .db_base import CveDatabase 

24from .registry import register_cve_db 

25 

26 

27class Spdx3AnnotEntry(AnnotDbEntry): 

28 """Represent one Vulnerability with associated VulnAssessmentRelationship""" 

29 

30 def __init__( 

31 self, 

32 parent_db: CveDatabase, 

33 cve_id: str, 

34 vuln: spdx30.security_Vulnerability, 

35 pkgs_rels: dict[ 

36 spdx30.software_Package, set[spdx30.security_VulnAssessmentRelationship] 

37 ], 

38 ) -> None: 

39 super().__init__(parent_db) 

40 self._cve_id: str = cve_id 

41 self._vuln = vuln 

42 self._pkgs_rels = pkgs_rels 

43 

44 @property 

45 def identifier(self) -> CveId: 

46 return CveId(self._cve_id) 

47 

48 @property 

49 def date_published(self) -> datetime | None: 

50 return self._vuln.security_publishedTime # type: ignore[no-any-return] 

51 

52 @property 

53 def date_modified(self) -> datetime | None: 

54 return self._vuln.security_modifiedTime # type: ignore[no-any-return] 

55 

56 @property 

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

58 d = self._vuln.description 

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

60 

61 @property 

62 def cvss_metrics(self) -> list[CvssMetric]: 

63 metrics: list[CvssMetric] = [] 

64 for rels in self._pkgs_rels.values(): 

65 for rel in rels: 

66 metric = lib_spdx3.cvss_metric_from_vuln_relationship(rel) 

67 if metric is not None: 

68 metrics.append(metric) 

69 return metrics 

70 

71 @property 

72 def external_refs(self) -> set[CveExtReference]: 

73 ext_refs: set[CveExtReference] = set() 

74 for ext_ref in self._vuln.externalRef: 

75 ext_refs.add(CveExtReference(ext_ref.locator, ext_ref.externalRefType)) 

76 return ext_refs 

77 

78 @property 

79 def vex_assessment(self) -> CveVexAssessment | None: 

80 assessment: CveVexAssessment | None = None 

81 for rels in self._pkgs_rels.values(): 

82 for rel in rels: 

83 a = lib_spdx3.cve_vex_assessment_from_vuln_relationship(rel) 

84 assessment = find_most_appropriate_assessment(assessment, a) 

85 return assessment 

86 

87 def _iterate_cpes(self, pkg: Any) -> Generator[Cpe23, None, None]: 

88 package: spdx30.software_Package = pkg 

89 for ext_id in package.externalIdentifier: 

90 if ext_id.externalIdentifierType == spdx30.ExternalIdentifierType.cpe23: 

91 cpe = Cpe23.parse(ext_id.identifier) 

92 if cpe: 

93 yield cpe 

94 

95 def get_associated_sem_ver_ranges( 

96 self, comp_ids: Iterable[CompId] 

97 ) -> Iterable[SemVerRange]: 

98 for package in self._pkgs_rels: 

99 yield from self._get_associated_sem_ver_ranges( 

100 comp_ids, package.software_packageVersion, package 

101 ) 

102 

103 def _iterate_applicable_comp_ids(self) -> Generator[CompId, None, None]: 

104 for package in self._pkgs_rels: 

105 for ids in self._get_vers_comp_ids(package).values(): 

106 yield from ids 

107 

108 

109@register_cve_db("spdx3-file") 

110class Spdx3AnnotDatabase(AnnotDatabase): 

111 def __init__( 

112 self, 

113 path: pathlib.Path | None, 

114 name: str, 

115 objset: lib_spdx3.ObjectSet | None = None, 

116 **kwargs: Any, 

117 ) -> None: 

118 super().__init__(name, **kwargs) 

119 if (path is None) and (objset is None): 

120 raise ValueError("Path to SPDX3 file is missing") 

121 self._obj: lib_spdx3.ObjectSet | None = objset 

122 self._path_spdx: pathlib.Path | None = None 

123 if path is not None: 

124 self._path_spdx = path.resolve(strict=True) 

125 self._vulns: dict[CveId, Spdx3AnnotEntry] = {} 

126 

127 def _initialize(self) -> None: 

128 if self._obj is None: 

129 assert self._path_spdx is not None 

130 self._obj = lib_spdx3.ObjectSet.parse_jsonld(self._path_spdx) 

131 self._vulns.clear() 

132 for vuln, pkgs_rels in self._obj.get_all_vulns().items(): 

133 cve_id = lib_spdx3.vuln_get_cve_id(vuln) 

134 if cve_id is not None: 

135 self._vulns[CveId(cve_id)] = Spdx3AnnotEntry( 

136 self, cve_id, vuln, pkgs_rels 

137 ) 

138 

139 def get_cve(self, cve_id: CveId) -> Spdx3AnnotEntry | None: 

140 return self._vulns.get(cve_id) 

141 

142 def iterate_cves(self) -> Generator[Spdx3AnnotEntry, None, None]: 

143 yield from self._vulns.values()