11#!/usr/bin/env python
22
33"""
4- Fetch NVD data from the JSON 1.1 feed archives, parse them into API 2.0
5- schema and save into files.
4+ Fetch NVD data from the JSON 2.0 feed archives and save into files.
65"""
76
87import gzip
1716 level = logging .INFO ,
1817 format = "%(asctime)s: %(levelname)s: %(name)s %(message)s" )
1918
19+ DEFAULT_BASE_URL = "https://nvd.nist.gov/feeds/json/cve/2.0/"
2020
21- def convert_cve_feed_to_api (cve_feed ):
22- """Convert NVDCVEFeedJSON10DefCVEItem to CVEAPIJSON20DefCVEItem."""
21+ class FeedLoader :
2322
24- # Required fields.
23+ log = logging . getLogger ( "FeedLoader" )
2524
26- cve_api = {"id" : cve_feed ['cve' ]['CVE_data_meta' ]['ID' ],
27- "published" : cve_feed ['publishedDate' ],
28- "lastModified" : cve_feed ['lastModifiedDate' ],
29- "descriptions" : [],
30- "metrics" : {}}
31-
32- # Description.
33-
34- for desc in cve_feed ['cve' ].get ('description' , {}).get ('description_data' , []):
35- cve_api ["descriptions" ].append ({"lang" : desc ["lang" ],
36- "value" : desc ["value" ]})
37-
38- # Metrics.
39-
40- impact = cve_feed .get ('impact' , {})
41- cvssMetricV2 = impact .get ('baseMetricV2' , {}).get ('cvssV2' , {})
42- cvssMetricV3 = impact .get ('baseMetricV3' , {}).get ('cvssV3' , {})
43-
44- if cvssMetricV2 :
45- key = "cvssMetricV2"
46- metric = {"type" : "Primary" ,
47- "cvssData" : {
48- "version" : "2.0" ,
49- "vectorString" : cvssMetricV2 ["vectorString" ],
50- "baseScore" : cvssMetricV2 ["baseScore" ]}}
51- cve_api ["metrics" ][key ] = [metric ]
52-
53- if cvssMetricV3 :
54- version = cvssMetricV3 ["version" ]
55- key = "cvssMetricV30" if version == "3.0" else "cvssMetricV31"
56- metric = {"type" : "Primary" ,
57- "cvssData" : {
58- "version" : version ,
59- "vectorString" : cvssMetricV3 ["vectorString" ],
60- "baseScore" : cvssMetricV3 ["baseScore" ]}}
61- cve_api ["metrics" ][key ] = [metric ]
62-
63- return {"cve" : cve_api }
64-
65-
66- class LegacyLoader :
67-
68- BASE_URL = "https://nvd.nist.gov/feeds/json/cve/1.1/"
69-
70- log = logging .getLogger ("LegacyLoader" )
71-
72- def __init__ (self , base_url = None ):
73- self ._base_url = base_url or self .BASE_URL
25+ def __init__ (self , base_url ):
26+ self ._base_url = base_url
7427
7528 def fetch (self , year ):
76- url = parse .urljoin (self ._base_url , f"nvdcve-1.1 -{ year } .json.gz" )
29+ url = parse .urljoin (self ._base_url , f"nvdcve-2.0 -{ year } .json.gz" )
7730 self .log .info ("fetching and decompressing: %s" , url )
7831 with request .urlopen (url , timeout = 60 ) as resp :
7932 with gzip .GzipFile (fileobj = resp ) as gz :
80- cve_feed = json .load (gz )
81- items = cve_feed .get ("CVE_Items" , [])
82- self .log .info ("cve count: %d" , len (items ))
83- yield from (convert_cve_feed_to_api (c ) for c in items )
33+ data = json .load (gz )
34+ yield from (v for v in data ["vulnerabilities" ]
35+ if v ["cve" ]["vulnStatus" ].lower () != "rejected" )
8436
8537
8638def parse_args ():
@@ -110,7 +62,8 @@ def dirpath(s):
11062 default = date .today ().year )
11163 parser .add_argument (
11264 "--base-url" ,
113- help = "URL for the NVD CVE feed (version 1.1)." )
65+ help = "URL for the NVD CVE feed (version 2.0)." ,
66+ default = DEFAULT_BASE_URL )
11467 parser .add_argument (
11568 "--file-pattern" ,
11669 help = "File name pattern of the NVD feed files." ,
@@ -120,7 +73,7 @@ def dirpath(s):
12073
12174
12275def main (args ):
123- loader = LegacyLoader (args .base_url )
76+ loader = FeedLoader (args .base_url )
12477 for year in range (args .start_year , args .end_year + 1 ):
12578 path = os .path .join (args .output , args .file_pattern % {'year' : year })
12679 logging .info ("fetching '%d' to '%s'" , year , path )
0 commit comments