Coverage Report - org.owasp.dependencycheck.data.update.NvdCveUpdater
 
Classes in this File Line Coverage Branch Coverage Complexity
NvdCveUpdater
0%
0/171
0%
0/72
11.5
 
 1  
 /*
 2  
  * This file is part of dependency-check-core.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  *
 16  
  * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
 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  
  * Class responsible for updating the NVD CVE data.
 47  
  *
 48  
  * @author Jeremy Long
 49  
  */
 50  0
 public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource {
 51  
 
 52  
     /**
 53  
      * The logger.
 54  
      */
 55  0
     private static final Logger LOGGER = LoggerFactory.getLogger(NvdCveUpdater.class);
 56  
     /**
 57  
      * The max thread pool size to use when downloading files.
 58  
      */
 59  0
     public static final int MAX_THREAD_POOL_SIZE = Settings.getInt(Settings.KEYS.MAX_DOWNLOAD_THREAD_POOL_SIZE, 3);
 60  
 
 61  
     /**
 62  
      * Downloads the latest NVD CVE XML file from the web and imports it into
 63  
      * the current CVE Database.
 64  
      *
 65  
      * @throws UpdateException is thrown if there is an error updating the
 66  
      * database
 67  
      */
 68  
     @Override
 69  
     public void update() throws UpdateException {
 70  
         try {
 71  0
             if (!Settings.getBoolean(Settings.KEYS.UPDATE_NVDCVE_ENABLED, true)) {
 72  0
                 return;
 73  
             }
 74  0
         } catch (InvalidSettingException ex) {
 75  0
             LOGGER.trace("inavlid setting UPDATE_NVDCVE_ENABLED", ex);
 76  0
         }
 77  
 
 78  
         try {
 79  0
             openDataStores();
 80  0
             boolean autoUpdate = true;
 81  
             try {
 82  0
                 autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
 83  0
             } catch (InvalidSettingException ex) {
 84  0
                 LOGGER.debug("Invalid setting for auto-update; using true.");
 85  0
             }
 86  0
             if (autoUpdate && checkUpdate()) {
 87  0
                 final UpdateableNvdCve updateable = getUpdatesNeeded();
 88  0
                 if (updateable.isUpdateNeeded()) {
 89  0
                     performUpdate(updateable);
 90  
                 }
 91  0
                 getProperties().save(DatabaseProperties.LAST_CHECKED, Long.toString(System.currentTimeMillis()));
 92  
             }
 93  0
         } catch (MalformedURLException ex) {
 94  0
             throw new UpdateException("NVD CVE properties files contain an invalid URL, unable to update the data to use the most current data.", ex);
 95  0
         } catch (DownloadFailedException ex) {
 96  0
             LOGGER.warn(
 97  
                     "Unable to download the NVD CVE data; the results may not include the most recent CPE/CVEs from the NVD.");
 98  0
             if (Settings.getString(Settings.KEYS.PROXY_SERVER) == null) {
 99  0
                 LOGGER.info(
 100  
                         "If you are behind a proxy you may need to configure dependency-check to use the proxy.");
 101  
             }
 102  0
             throw new UpdateException("Unable to download the NVD CVE data.", ex);
 103  
         } finally {
 104  0
             closeDataStores();
 105  0
         }
 106  0
     }
 107  
 
 108  
     /**
 109  
      * Checks if the NVD CVE XML files were last checked recently. As an
 110  
      * optimization, we can avoid repetitive checks against the NVD. Setting
 111  
      * CVE_CHECK_VALID_FOR_HOURS determines the duration since last check before
 112  
      * checking again. A database property stores the timestamp of the last
 113  
      * check.
 114  
      *
 115  
      * @return true to proceed with the check, or false to skip
 116  
      * @throws UpdateException thrown when there is an issue checking for
 117  
      * updates
 118  
      */
 119  
     private boolean checkUpdate() throws UpdateException {
 120  0
         boolean proceed = true;
 121  
         // If the valid setting has not been specified, then we proceed to check...
 122  0
         final int validForHours = Settings.getInt(Settings.KEYS.CVE_CHECK_VALID_FOR_HOURS, 0);
 123  0
         if (dataExists() && 0 < validForHours) {
 124  
             // ms Valid = valid (hours) x 60 min/hour x 60 sec/min x 1000 ms/sec
 125  0
             final long msValid = validForHours * 60L * 60L * 1000L;
 126  0
             final long lastChecked = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_CHECKED, "0"));
 127  0
             final long now = System.currentTimeMillis();
 128  0
             proceed = (now - lastChecked) > msValid;
 129  0
             if (!proceed) {
 130  0
                 LOGGER.info("Skipping NVD check since last check was within {} hours.", validForHours);
 131  0
                 LOGGER.debug("Last NVD was at {}, and now {} is within {} ms.",
 132  0
                         lastChecked, now, msValid);
 133  
             }
 134  
         }
 135  0
         return proceed;
 136  
     }
 137  
 
 138  
     /**
 139  
      * Checks the CVE Index to ensure data exists and analysis can continue.
 140  
      *
 141  
      * @return true if the database contains data
 142  
      */
 143  
     private boolean dataExists() {
 144  0
         CveDB cve = null;
 145  
         try {
 146  0
             cve = new CveDB();
 147  0
             cve.open();
 148  0
             return cve.dataExists();
 149  0
         } catch (DatabaseException ex) {
 150  0
             return false;
 151  
         } finally {
 152  0
             if (cve != null) {
 153  0
                 cve.close();
 154  
             }
 155  
         }
 156  
     }
 157  
 
 158  
     /**
 159  
      * Downloads the latest NVD CVE XML file from the web and imports it into
 160  
      * the current CVE Database.
 161  
      *
 162  
      * @param updateable a collection of NVD CVE data file references that need
 163  
      * to be downloaded and processed to update the database
 164  
      * @throws UpdateException is thrown if there is an error updating the
 165  
      * database
 166  
      */
 167  
     private void performUpdate(UpdateableNvdCve updateable) throws UpdateException {
 168  0
         int maxUpdates = 0;
 169  0
         for (NvdCveInfo cve : updateable) {
 170  0
             if (cve.getNeedsUpdate()) {
 171  0
                 maxUpdates += 1;
 172  
             }
 173  0
         }
 174  0
         if (maxUpdates <= 0) {
 175  0
             return;
 176  
         }
 177  0
         if (maxUpdates > 3) {
 178  0
             LOGGER.info("NVD CVE requires several updates; this could take a couple of minutes.");
 179  
         }
 180  
 
 181  0
         final int poolSize = (MAX_THREAD_POOL_SIZE < maxUpdates) ? MAX_THREAD_POOL_SIZE : maxUpdates;
 182  
 
 183  0
         final ExecutorService downloadExecutors = Executors.newFixedThreadPool(poolSize);
 184  0
         final ExecutorService processExecutor = Executors.newSingleThreadExecutor();
 185  0
         final Set<Future<Future<ProcessTask>>> downloadFutures = new HashSet<Future<Future<ProcessTask>>>(maxUpdates);
 186  0
         for (NvdCveInfo cve : updateable) {
 187  0
             if (cve.getNeedsUpdate()) {
 188  0
                 final DownloadTask call = new DownloadTask(cve, processExecutor, getCveDB(), Settings.getInstance());
 189  0
                 downloadFutures.add(downloadExecutors.submit(call));
 190  
             }
 191  0
         }
 192  0
         downloadExecutors.shutdown();
 193  
 
 194  
         //next, move the future future processTasks to just future processTasks
 195  0
         final Set<Future<ProcessTask>> processFutures = new HashSet<Future<ProcessTask>>(maxUpdates);
 196  0
         for (Future<Future<ProcessTask>> future : downloadFutures) {
 197  0
             Future<ProcessTask> task = null;
 198  
             try {
 199  0
                 task = future.get();
 200  0
             } catch (InterruptedException ex) {
 201  0
                 downloadExecutors.shutdownNow();
 202  0
                 processExecutor.shutdownNow();
 203  
 
 204  0
                 LOGGER.debug("Thread was interrupted during download", ex);
 205  0
                 throw new UpdateException("The download was interrupted", ex);
 206  0
             } catch (ExecutionException ex) {
 207  0
                 downloadExecutors.shutdownNow();
 208  0
                 processExecutor.shutdownNow();
 209  
 
 210  0
                 LOGGER.debug("Thread was interrupted during download execution", ex);
 211  0
                 throw new UpdateException("The execution of the download was interrupted", ex);
 212  0
             }
 213  0
             if (task == null) {
 214  0
                 downloadExecutors.shutdownNow();
 215  0
                 processExecutor.shutdownNow();
 216  0
                 LOGGER.debug("Thread was interrupted during download");
 217  0
                 throw new UpdateException("The download was interrupted; unable to complete the update");
 218  
             } else {
 219  0
                 processFutures.add(task);
 220  
             }
 221  0
         }
 222  
 
 223  0
         for (Future<ProcessTask> future : processFutures) {
 224  
             try {
 225  0
                 final ProcessTask task = future.get();
 226  0
                 if (task.getException() != null) {
 227  0
                     throw task.getException();
 228  
                 }
 229  0
             } catch (InterruptedException ex) {
 230  0
                 processExecutor.shutdownNow();
 231  0
                 LOGGER.debug("Thread was interrupted during processing", ex);
 232  0
                 throw new UpdateException(ex);
 233  0
             } catch (ExecutionException ex) {
 234  0
                 processExecutor.shutdownNow();
 235  0
                 LOGGER.debug("Execution Exception during process", ex);
 236  0
                 throw new UpdateException(ex);
 237  
             } finally {
 238  0
                 processExecutor.shutdown();
 239  0
             }
 240  0
         }
 241  
 
 242  0
         if (maxUpdates >= 1) { //ensure the modified file date gets written (we may not have actually updated it)
 243  0
             getProperties().save(updateable.get(MODIFIED));
 244  0
             LOGGER.info("Begin database maintenance.");
 245  0
             getCveDB().cleanupDatabase();
 246  0
             LOGGER.info("End database maintenance.");
 247  
         }
 248  0
     }
 249  
 
 250  
     /**
 251  
      * Determines if the index needs to be updated. This is done by fetching the
 252  
      * NVD CVE meta data and checking the last update date. If the data needs to
 253  
      * be refreshed this method will return the NvdCveUrl for the files that
 254  
      * need to be updated.
 255  
      *
 256  
      * @return the collection of files that need to be updated
 257  
      * @throws MalformedURLException is thrown if the URL for the NVD CVE Meta
 258  
      * data is incorrect
 259  
      * @throws DownloadFailedException is thrown if there is an error.
 260  
      * downloading the NVD CVE download data file
 261  
      * @throws UpdateException Is thrown if there is an issue with the last
 262  
      * updated properties file
 263  
      */
 264  
     protected final UpdateableNvdCve getUpdatesNeeded() throws MalformedURLException, DownloadFailedException, UpdateException {
 265  0
         UpdateableNvdCve updates = null;
 266  
         try {
 267  0
             updates = retrieveCurrentTimestampsFromWeb();
 268  0
         } catch (InvalidDataException ex) {
 269  0
             final String msg = "Unable to retrieve valid timestamp from nvd cve downloads page";
 270  0
             LOGGER.debug(msg, ex);
 271  0
             throw new DownloadFailedException(msg, ex);
 272  0
         } catch (InvalidSettingException ex) {
 273  0
             LOGGER.debug("Invalid setting found when retrieving timestamps", ex);
 274  0
             throw new DownloadFailedException("Invalid settings", ex);
 275  0
         }
 276  
 
 277  0
         if (updates == null) {
 278  0
             throw new DownloadFailedException("Unable to retrieve the timestamps of the currently published NVD CVE data");
 279  
         }
 280  0
         if (!getProperties().isEmpty()) {
 281  
             try {
 282  0
                 final int startYear = Settings.getInt(Settings.KEYS.CVE_START_YEAR, 2002);
 283  0
                 final int endYear = Calendar.getInstance().get(Calendar.YEAR);
 284  0
                 boolean needsFullUpdate = false;
 285  0
                 for (int y = startYear; y <= endYear; y++) {
 286  0
                     final long val = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED_BASE + y, "0"));
 287  0
                     if (val == 0) {
 288  0
                         needsFullUpdate = true;
 289  
                     }
 290  
                 }
 291  
 
 292  0
                 final long lastUpdated = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED, "0"));
 293  0
                 final long now = System.currentTimeMillis();
 294  0
                 final int days = Settings.getInt(Settings.KEYS.CVE_MODIFIED_VALID_FOR_DAYS, 7);
 295  0
                 if (!needsFullUpdate && lastUpdated == updates.getTimeStamp(MODIFIED)) {
 296  0
                     updates.clear(); //we don't need to update anything.
 297  0
                 } else if (!needsFullUpdate && DateUtil.withinDateRange(lastUpdated, now, days)) {
 298  0
                     for (NvdCveInfo entry : updates) {
 299  0
                         if (MODIFIED.equals(entry.getId())) {
 300  0
                             entry.setNeedsUpdate(true);
 301  
                         } else {
 302  0
                             entry.setNeedsUpdate(false);
 303  
                         }
 304  0
                     }
 305  
                 } else { //we figure out which of the several XML files need to be downloaded.
 306  0
                     for (NvdCveInfo entry : updates) {
 307  0
                         if (MODIFIED.equals(entry.getId())) {
 308  0
                             entry.setNeedsUpdate(true);
 309  
                         } else {
 310  0
                             long currentTimestamp = 0;
 311  
                             try {
 312  0
                                 currentTimestamp = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED_BASE
 313  0
                                         + entry.getId(), "0"));
 314  0
                             } catch (NumberFormatException ex) {
 315  0
                                 LOGGER.debug("Error parsing '{}' '{}' from nvdcve.lastupdated",
 316  0
                                         DatabaseProperties.LAST_UPDATED_BASE, entry.getId(), ex);
 317  0
                             }
 318  0
                             if (currentTimestamp == entry.getTimestamp()) {
 319  0
                                 entry.setNeedsUpdate(false);
 320  
                             }
 321  
                         }
 322  0
                     }
 323  
                 }
 324  0
             } catch (NumberFormatException ex) {
 325  0
                 LOGGER.warn("An invalid schema version or timestamp exists in the data.properties file.");
 326  0
                 LOGGER.debug("", ex);
 327  0
             }
 328  
         }
 329  0
         return updates;
 330  
     }
 331  
 
 332  
     /**
 333  
      * Retrieves the timestamps from the NVD CVE meta data file.
 334  
      *
 335  
      * @return the timestamp from the currently published nvdcve downloads page
 336  
      * @throws MalformedURLException thrown if the URL for the NVD CCE Meta data
 337  
      * is incorrect.
 338  
      * @throws DownloadFailedException thrown if there is an error downloading
 339  
      * the nvd cve meta data file
 340  
      * @throws InvalidDataException thrown if there is an exception parsing the
 341  
      * timestamps
 342  
      * @throws InvalidSettingException thrown if the settings are invalid
 343  
      */
 344  
     private UpdateableNvdCve retrieveCurrentTimestampsFromWeb()
 345  
             throws MalformedURLException, DownloadFailedException, InvalidDataException, InvalidSettingException {
 346  
 
 347  0
         final UpdateableNvdCve updates = new UpdateableNvdCve();
 348  0
         updates.add(MODIFIED, Settings.getString(Settings.KEYS.CVE_MODIFIED_20_URL),
 349  0
                 Settings.getString(Settings.KEYS.CVE_MODIFIED_12_URL),
 350  
                 false);
 351  
 
 352  0
         final int start = Settings.getInt(Settings.KEYS.CVE_START_YEAR);
 353  0
         final int end = Calendar.getInstance().get(Calendar.YEAR);
 354  0
         final String baseUrl20 = Settings.getString(Settings.KEYS.CVE_SCHEMA_2_0);
 355  0
         final String baseUrl12 = Settings.getString(Settings.KEYS.CVE_SCHEMA_1_2);
 356  0
         for (int i = start; i <= end; i++) {
 357  0
             updates.add(Integer.toString(i), String.format(baseUrl20, i),
 358  0
                     String.format(baseUrl12, i),
 359  
                     true);
 360  
         }
 361  0
         return updates;
 362  
     }
 363  
 }