support/scripts/cve-check: fix vulnerability timestamp to RFC 3339

Normalize vulnerability timestamps to RFC 3339 format with explicit UTC
timezone suffix for CycloneDX 1.6 compliance.
This fixes validation errors in sbom-utility and makes the generated
SBOM with vulnerabilities compatible with DependencyTrack VEX parsers.

The NVD JSON data feeds provide timestamps in ISO 8601 format without timezone
information (e.g., "1999-01-01T05:00:00.000"), but CycloneDX 1.6 requires
RFC 3339 format with explicit timezone designation (e.g.,
"1999-01-01T05:00:00.000Z").

Add nvd_datetime_to_rfc3339() helper function to convert timestamps before
serialization.

Validation results:

Before fix:
  $ sbom-utility validate -i cve/cve_report_current.json
  [INFO] BOM valid against JSON schema: 'false'
  [INFO] (234) schema errors detected.

  Error example:
  {
    "type": "format",
    "field": "vulnerabilities.0.updated",
    "context": "(root).vulnerabilities.0.updated",
    "description": "Does not match format 'date-time'",
    "value": "2025-04-03T01:03:51.193"
  }

After fix:
  $ sbom-utility validate -i cve/cve_report_update.json
  [INFO] BOM valid against JSON schema: 'true'

Tested-with: sbom-utility v0.18.1
Co-authored-by: Fabien Lehoussel <fabien.lehoussel@smile.fr>
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
(cherry picked from commit e46783d3a0)
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
This commit is contained in:
Thomas Perale
2026-05-29 17:06:27 +02:00
parent ee0ee0b33e
commit df418f0675

View File

@@ -12,10 +12,10 @@
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
from typing import TypedDict from typing import TypedDict
from datetime import datetime, timezone
import argparse import argparse
import sys import sys
import json import json
import cve as cvecheck import cve as cvecheck
@@ -35,6 +35,24 @@ locally.
brpath = Path(__file__).parent.parent.parent brpath = Path(__file__).parent.parent.parent
def datetime_to_rfc3339(dt_string):
"""Normalize datetime string to RFC 3339 format with Z suffix.
NVD dates are already in ISO format, just need to add the Z suffix.
Input: "1999-01-01T05:00:00.000"
Output: "1999-01-01T05:00:00.000Z"
"""
dt = datetime.fromisoformat(dt_string.replace('Z', '+00:00'))
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
else:
dt = dt.astimezone(timezone.utc)
return dt.isoformat().replace('+00:00', 'Z')
def cve_api_get_lang_from_list(values, lang="en") -> (str | None): def cve_api_get_lang_from_list(values, lang="en") -> (str | None):
for x in values: for x in values:
if x.get("lang") == lang: if x.get("lang") == lang:
@@ -134,10 +152,10 @@ def nvd_cve_to_cdx_vulnerability(nvd_cve):
"url": f"https://nvd.nist.gov/vuln/detail/{nvd_cve['id']}" "url": f"https://nvd.nist.gov/vuln/detail/{nvd_cve['id']}"
}, },
**({ **({
"published": nvd_cve["published"], "published": datetime_to_rfc3339(nvd_cve["published"]),
} if "published" in nvd_cve else {}), } if "published" in nvd_cve else {}),
**({ **({
"updated": nvd_cve["lastModified"], "updated": datetime_to_rfc3339(nvd_cve["lastModified"]),
} if "lastModified" in nvd_cve else {}), } if "lastModified" in nvd_cve else {}),
**({ **({
"cwes": nvd_cve_weaknesses_to_cdx(nvd_cve["weaknesses"]), "cwes": nvd_cve_weaknesses_to_cdx(nvd_cve["weaknesses"]),