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
« 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
4import pathlib
5from collections.abc import Generator, Iterable
6from datetime import datetime
7from typing import Any
9from spdx_python_model.bindings import v3_0_1 as spdx30 # type: ignore[import-untyped]
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
27class Spdx3AnnotEntry(AnnotDbEntry):
28 """Represent one Vulnerability with associated VulnAssessmentRelationship"""
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
44 @property
45 def identifier(self) -> CveId:
46 return CveId(self._cve_id)
48 @property
49 def date_published(self) -> datetime | None:
50 return self._vuln.security_publishedTime # type: ignore[no-any-return]
52 @property
53 def date_modified(self) -> datetime | None:
54 return self._vuln.security_modifiedTime # type: ignore[no-any-return]
56 @property
57 def description(self) -> str | None:
58 d = self._vuln.description
59 return d if isinstance(d, str) else None
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
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
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
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
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 )
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
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] = {}
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 )
139 def get_cve(self, cve_id: CveId) -> Spdx3AnnotEntry | None:
140 return self._vulns.get(cve_id)
142 def iterate_cves(self) -> Generator[Spdx3AnnotEntry, None, None]:
143 yield from self._vulns.values()