1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.data.update;
19
20 import java.net.MalformedURLException;
21 import java.util.Calendar;
22 import java.util.HashSet;
23 import java.util.Set;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.ExecutorService;
26 import java.util.concurrent.Executors;
27 import java.util.concurrent.Future;
28 import org.owasp.dependencycheck.data.nvdcve.CveDB;
29 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
30 import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
31 import static org.owasp.dependencycheck.data.nvdcve.DatabaseProperties.MODIFIED;
32 import org.owasp.dependencycheck.data.update.exception.InvalidDataException;
33 import org.owasp.dependencycheck.data.update.exception.UpdateException;
34 import org.owasp.dependencycheck.data.update.nvd.DownloadTask;
35 import org.owasp.dependencycheck.data.update.nvd.NvdCveInfo;
36 import org.owasp.dependencycheck.data.update.nvd.ProcessTask;
37 import org.owasp.dependencycheck.data.update.nvd.UpdateableNvdCve;
38 import org.owasp.dependencycheck.utils.DateUtil;
39 import org.owasp.dependencycheck.utils.DownloadFailedException;
40 import org.owasp.dependencycheck.utils.InvalidSettingException;
41 import org.owasp.dependencycheck.utils.Settings;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45
46
47
48
49
50 public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource {
51
52
53
54
55 private static final Logger LOGGER = LoggerFactory.getLogger(NvdCveUpdater.class);
56
57
58
59 public static final int MAX_THREAD_POOL_SIZE = Settings.getInt(Settings.KEYS.MAX_DOWNLOAD_THREAD_POOL_SIZE, 3);
60
61
62
63
64
65
66
67
68 @Override
69 public void update() throws UpdateException {
70 try {
71 openDataStores();
72 boolean autoUpdate = true;
73 try {
74 autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
75 } catch (InvalidSettingException ex) {
76 LOGGER.debug("Invalid setting for auto-update; using true.");
77 }
78 if (autoUpdate && checkUpdate()) {
79 final UpdateableNvdCve updateable = getUpdatesNeeded();
80 if (updateable.isUpdateNeeded()) {
81 performUpdate(updateable);
82 }
83 getProperties().save(DatabaseProperties.LAST_CHECKED, Long.toString(System.currentTimeMillis()));
84 }
85 } catch (MalformedURLException ex) {
86 throw new UpdateException("NVD CVE properties files contain an invalid URL, unable to update the data to use the most current data.", ex);
87 } catch (DownloadFailedException ex) {
88 LOGGER.warn(
89 "Unable to download the NVD CVE data; the results may not include the most recent CPE/CVEs from the NVD.");
90 if (Settings.getString(Settings.KEYS.PROXY_SERVER) == null) {
91 LOGGER.info(
92 "If you are behind a proxy you may need to configure dependency-check to use the proxy.");
93 }
94 throw new UpdateException("Unable to download the NVD CVE data.", ex);
95 } finally {
96 closeDataStores();
97 }
98 }
99
100
101
102
103
104
105
106
107
108
109
110
111 private boolean checkUpdate() throws UpdateException {
112 boolean proceed = true;
113
114 final int validForHours = Settings.getInt(Settings.KEYS.CVE_CHECK_VALID_FOR_HOURS, 0);
115 if (dataExists() && 0 < validForHours) {
116
117 final long msValid = validForHours * 60L * 60L * 1000L;
118 final long lastChecked = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_CHECKED, "0"));
119 final long now = System.currentTimeMillis();
120 proceed = (now - lastChecked) > msValid;
121 if (!proceed) {
122 LOGGER.info("Skipping NVD check since last check was within {} hours.", validForHours);
123 LOGGER.debug("Last NVD was at {}, and now {} is within {} ms.",
124 lastChecked, now, msValid);
125 }
126 }
127 return proceed;
128 }
129
130
131
132
133
134
135 private boolean dataExists() {
136 CveDB cve = null;
137 try {
138 cve = new CveDB();
139 cve.open();
140 return cve.dataExists();
141 } catch (DatabaseException ex) {
142 return false;
143 } finally {
144 if (cve != null) {
145 cve.close();
146 }
147 }
148 }
149
150
151
152
153
154
155
156
157
158
159 private void performUpdate(UpdateableNvdCve updateable) throws UpdateException {
160 int maxUpdates = 0;
161 for (NvdCveInfo cve : updateable) {
162 if (cve.getNeedsUpdate()) {
163 maxUpdates += 1;
164 }
165 }
166 if (maxUpdates <= 0) {
167 return;
168 }
169 if (maxUpdates > 3) {
170 LOGGER.info("NVD CVE requires several updates; this could take a couple of minutes.");
171 }
172
173 final int poolSize = (MAX_THREAD_POOL_SIZE < maxUpdates) ? MAX_THREAD_POOL_SIZE : maxUpdates;
174
175 final ExecutorService downloadExecutors = Executors.newFixedThreadPool(poolSize);
176 final ExecutorService processExecutor = Executors.newSingleThreadExecutor();
177 final Set<Future<Future<ProcessTask>>> downloadFutures = new HashSet<Future<Future<ProcessTask>>>(maxUpdates);
178 for (NvdCveInfo cve : updateable) {
179 if (cve.getNeedsUpdate()) {
180 final DownloadTask call = new DownloadTask(cve, processExecutor, getCveDB(), Settings.getInstance());
181 downloadFutures.add(downloadExecutors.submit(call));
182 }
183 }
184 downloadExecutors.shutdown();
185
186
187 final Set<Future<ProcessTask>> processFutures = new HashSet<Future<ProcessTask>>(maxUpdates);
188 for (Future<Future<ProcessTask>> future : downloadFutures) {
189 Future<ProcessTask> task = null;
190 try {
191 task = future.get();
192 } catch (InterruptedException ex) {
193 downloadExecutors.shutdownNow();
194 processExecutor.shutdownNow();
195
196 LOGGER.debug("Thread was interrupted during download", ex);
197 throw new UpdateException("The download was interrupted", ex);
198 } catch (ExecutionException ex) {
199 downloadExecutors.shutdownNow();
200 processExecutor.shutdownNow();
201
202 LOGGER.debug("Thread was interrupted during download execution", ex);
203 throw new UpdateException("The execution of the download was interrupted", ex);
204 }
205 if (task == null) {
206 downloadExecutors.shutdownNow();
207 processExecutor.shutdownNow();
208 LOGGER.debug("Thread was interrupted during download");
209 throw new UpdateException("The download was interrupted; unable to complete the update");
210 } else {
211 processFutures.add(task);
212 }
213 }
214
215 for (Future<ProcessTask> future : processFutures) {
216 try {
217 final ProcessTask task = future.get();
218 if (task.getException() != null) {
219 throw task.getException();
220 }
221 } catch (InterruptedException ex) {
222 processExecutor.shutdownNow();
223 LOGGER.debug("Thread was interrupted during processing", ex);
224 throw new UpdateException(ex);
225 } catch (ExecutionException ex) {
226 processExecutor.shutdownNow();
227 LOGGER.debug("Execution Exception during process", ex);
228 throw new UpdateException(ex);
229 } finally {
230 processExecutor.shutdown();
231 }
232 }
233
234 if (maxUpdates >= 1) {
235 getProperties().save(updateable.get(MODIFIED));
236 LOGGER.info("Begin database maintenance.");
237 getCveDB().cleanupDatabase();
238 LOGGER.info("End database maintenance.");
239 }
240 }
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256 protected final UpdateableNvdCve getUpdatesNeeded() throws MalformedURLException, DownloadFailedException, UpdateException {
257 UpdateableNvdCve updates = null;
258 try {
259 updates = retrieveCurrentTimestampsFromWeb();
260 } catch (InvalidDataException ex) {
261 final String msg = "Unable to retrieve valid timestamp from nvd cve downloads page";
262 LOGGER.debug(msg, ex);
263 throw new DownloadFailedException(msg, ex);
264 } catch (InvalidSettingException ex) {
265 LOGGER.debug("Invalid setting found when retrieving timestamps", ex);
266 throw new DownloadFailedException("Invalid settings", ex);
267 }
268
269 if (updates == null) {
270 throw new DownloadFailedException("Unable to retrieve the timestamps of the currently published NVD CVE data");
271 }
272 if (!getProperties().isEmpty()) {
273 try {
274 final long lastUpdated = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED, "0"));
275 final long now = System.currentTimeMillis();
276 final int days = Settings.getInt(Settings.KEYS.CVE_MODIFIED_VALID_FOR_DAYS, 7);
277 if (lastUpdated == updates.getTimeStamp(MODIFIED)) {
278 updates.clear();
279 } else if (DateUtil.withinDateRange(lastUpdated, now, days)) {
280 for (NvdCveInfo entry : updates) {
281 if (MODIFIED.equals(entry.getId())) {
282 entry.setNeedsUpdate(true);
283 } else {
284 entry.setNeedsUpdate(false);
285 }
286 }
287 } else {
288 for (NvdCveInfo entry : updates) {
289 if (MODIFIED.equals(entry.getId())) {
290 entry.setNeedsUpdate(true);
291 } else {
292 long currentTimestamp = 0;
293 try {
294 currentTimestamp = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED_BASE
295 + entry.getId(), "0"));
296 } catch (NumberFormatException ex) {
297 LOGGER.debug("Error parsing '{}' '{}' from nvdcve.lastupdated",
298 DatabaseProperties.LAST_UPDATED_BASE, entry.getId(), ex);
299 }
300 if (currentTimestamp == entry.getTimestamp()) {
301 entry.setNeedsUpdate(false);
302 }
303 }
304 }
305 }
306 } catch (NumberFormatException ex) {
307 LOGGER.warn("An invalid schema version or timestamp exists in the data.properties file.");
308 LOGGER.debug("", ex);
309 }
310 }
311 return updates;
312 }
313
314
315
316
317
318
319
320
321
322
323
324
325
326 private UpdateableNvdCve retrieveCurrentTimestampsFromWeb()
327 throws MalformedURLException, DownloadFailedException, InvalidDataException, InvalidSettingException {
328
329 final UpdateableNvdCve updates = new UpdateableNvdCve();
330 updates.add(MODIFIED, Settings.getString(Settings.KEYS.CVE_MODIFIED_20_URL),
331 Settings.getString(Settings.KEYS.CVE_MODIFIED_12_URL),
332 false);
333
334 final int start = Settings.getInt(Settings.KEYS.CVE_START_YEAR);
335 final int end = Calendar.getInstance().get(Calendar.YEAR);
336 final String baseUrl20 = Settings.getString(Settings.KEYS.CVE_SCHEMA_2_0);
337 final String baseUrl12 = Settings.getString(Settings.KEYS.CVE_SCHEMA_1_2);
338 for (int i = start; i <= end; i++) {
339 updates.add(Integer.toString(i), String.format(baseUrl20, i),
340 String.format(baseUrl12, i),
341 true);
342 }
343 return updates;
344 }
345 }