diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 000000000..f2a77a5bc --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,18 @@ +### Reporting Bugs/Errors +When reporting errors, 99% of the time log file output is required. Please post the log file as a [gist](https://gist.github.com/) and provide a link in the new issue. + +### Reporting False Positives +When reporting a false positive please include: +- The location of the dependency (Maven GAV, URL to download the dependency, etc.) +- The CPE that is believed to be false positive + - Please report the CPE not the CVE + +#### Example +False positive on library foo.jar - reported as cpe:/a:apache:tomcat:7.0 +```xml + + org.sample + foo + 1.0 + +``` \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5369f2ed3..0f79d7db7 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ _site/** .LCKpom.xml~ #coverity /cov-int/ -/dependency-check-core/nbproject/ \ No newline at end of file +/dependency-check-core/nbproject/ +cov-scan.bat \ No newline at end of file diff --git a/README.md b/README.md index 33d31804b..5b5a0c5f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Build Status](https://travis-ci.org/jeremylong/DependencyCheck.svg?branch=master)](https://travis-ci.org/jeremylong/DependencyCheck) [![Apache 2.0 License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.txt) +[![Build Status](https://travis-ci.org/jeremylong/DependencyCheck.svg?branch=master)](https://travis-ci.org/jeremylong/DependencyCheck) [![Apache 2.0 License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.txt) [![Coverity Scan Build Status](https://scan.coverity.com/projects/1654/badge.svg)](https://scan.coverity.com/projects/dependencycheck) + Dependency-Check ================ diff --git a/dependency-check-ant/pom.xml b/dependency-check-ant/pom.xml index 99ec93329..eed10c4f0 100644 --- a/dependency-check-ant/pom.xml +++ b/dependency-check-ant/pom.xml @@ -20,7 +20,7 @@ Copyright (c) 2013 - Jeremy Long. All Rights Reserved. org.owasp dependency-check-parent - 1.4.1-SNAPSHOT + 1.4.4-SNAPSHOT dependency-check-ant diff --git a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java index 78f6e823a..6c38786d4 100644 --- a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java +++ b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java @@ -18,7 +18,6 @@ package org.owasp.dependencycheck.taskdefs; import java.io.File; -import java.io.IOException; import java.util.List; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; @@ -32,9 +31,12 @@ import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.data.nvdcve.CveDB; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; +import org.owasp.dependencycheck.data.update.exception.UpdateException; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.exception.ExceptionCollection; +import org.owasp.dependencycheck.exception.ReportException; import org.owasp.dependencycheck.reporting.ReportGenerator; import org.owasp.dependencycheck.reporting.ReportGenerator.Format; import org.owasp.dependencycheck.utils.Settings; @@ -806,52 +808,67 @@ public class Check extends Update { engine = new Engine(Check.class.getClassLoader()); if (isUpdateOnly()) { log("Deprecated 'UpdateOnly' property set; please use the UpdateTask instead", Project.MSG_WARN); - engine.doUpdates(); - } else { try { - for (Resource resource : path) { - final FileProvider provider = resource.as(FileProvider.class); - if (provider != null) { - final File file = provider.getFile(); - if (file != null && file.exists()) { - engine.scan(file); - } + engine.doUpdates(); + } catch (UpdateException ex) { + if (this.isFailOnError()) { + throw new BuildException(ex); + } + log(ex.getMessage(), Project.MSG_ERR); + } + } else { + for (Resource resource : path) { + final FileProvider provider = resource.as(FileProvider.class); + if (provider != null) { + final File file = provider.getFile(); + if (file != null && file.exists()) { + engine.scan(file); } } + } + try { engine.analyzeDependencies(); - DatabaseProperties prop = null; - CveDB cve = null; - try { - cve = new CveDB(); - cve.open(); - prop = cve.getDatabaseProperties(); - } catch (DatabaseException ex) { - log("Unable to retrieve DB Properties", ex, Project.MSG_DEBUG); - } finally { - if (cve != null) { - cve.close(); - } + } catch (ExceptionCollection ex) { + if (this.isFailOnError()) { + throw new BuildException(ex); } - final ReportGenerator reporter = new ReportGenerator(getProjectName(), engine.getDependencies(), engine.getAnalyzers(), prop); - reporter.generateReports(reportOutputDirectory, reportFormat); + } + DatabaseProperties prop = null; + CveDB cve = null; + try { + cve = new CveDB(); + cve.open(); + prop = cve.getDatabaseProperties(); + } catch (DatabaseException ex) { + log("Unable to retrieve DB Properties", ex, Project.MSG_DEBUG); + } finally { + if (cve != null) { + cve.close(); + } + } + final ReportGenerator reporter = new ReportGenerator(getProjectName(), engine.getDependencies(), engine.getAnalyzers(), prop); + reporter.generateReports(reportOutputDirectory, reportFormat); - if (this.failBuildOnCVSS <= 10) { - checkForFailure(engine.getDependencies()); - } - if (this.showSummary) { - showSummary(engine.getDependencies()); - } - } catch (IOException ex) { - log("Unable to generate dependency-check report", ex, Project.MSG_DEBUG); - throw new BuildException("Unable to generate dependency-check report", ex); - } catch (Exception ex) { - log("An exception occurred; unable to continue task", ex, Project.MSG_DEBUG); - throw new BuildException("An exception occurred; unable to continue task", ex); + if (this.failBuildOnCVSS <= 10) { + checkForFailure(engine.getDependencies()); + } + if (this.showSummary) { + showSummary(engine.getDependencies()); } } } catch (DatabaseException ex) { - log("Unable to connect to the dependency-check database; analysis has stopped", ex, Project.MSG_ERR); + final String msg = "Unable to connect to the dependency-check database; analysis has stopped"; + if (this.isFailOnError()) { + throw new BuildException(msg, ex); + } + log(msg, ex, Project.MSG_ERR); + } catch (ReportException ex) { + final String msg = "Unable to generate the dependency-check report"; + if (this.isFailOnError()) { + throw new BuildException(msg, ex); + } + log(msg, ex, Project.MSG_ERR); } finally { Settings.cleanup(true); if (engine != null) { diff --git a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Purge.java b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Purge.java index ce28b0645..3bc335fb0 100644 --- a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Purge.java +++ b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Purge.java @@ -71,6 +71,30 @@ public class Purge extends Task { this.dataDirectory = dataDirectory; } + /** + * Indicates if dependency-check should fail the build if an exception + * occurs. + */ + private boolean failOnError = true; + + /** + * Get the value of failOnError. + * + * @return the value of failOnError + */ + public boolean isFailOnError() { + return failOnError; + } + + /** + * Set the value of failOnError. + * + * @param failOnError new value of failOnError + */ + public void setFailOnError(boolean failOnError) { + this.failOnError = failOnError; + } + @Override public void execute() throws BuildException { populateSettings(); @@ -81,30 +105,49 @@ public class Purge extends Task { if (db.delete()) { log("Database file purged; local copy of the NVD has been removed", Project.MSG_INFO); } else { - log(String.format("Unable to delete '%s'; please delete the file manually", db.getAbsolutePath()), Project.MSG_ERR); + final String msg = String.format("Unable to delete '%s'; please delete the file manually", db.getAbsolutePath()); + if (this.failOnError) { + throw new BuildException(msg); + } + log(msg, Project.MSG_ERR); } } else { - log(String.format("Unable to purge database; the database file does not exists: %s", db.getAbsolutePath()), Project.MSG_ERR); + final String msg = String.format("Unable to purge database; the database file does not exists: %s", db.getAbsolutePath()); + if (this.failOnError) { + throw new BuildException(msg); + } + log(msg, Project.MSG_ERR); } } catch (IOException ex) { - log("Unable to delete the database", Project.MSG_ERR); + final String msg = "Unable to delete the database"; + if (this.failOnError) { + throw new BuildException(msg); + } + log(msg, Project.MSG_ERR); } finally { Settings.cleanup(true); } } /** - * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system properties - * required to change the proxy server, port, and connection timeout. + * Takes the properties supplied and updates the dependency-check settings. + * Additionally, this sets the system properties required to change the + * proxy server, port, and connection timeout. + * + * @throws BuildException thrown if the properties file cannot be read. */ - protected void populateSettings() { + protected void populateSettings() throws BuildException { Settings.initialize(); InputStream taskProperties = null; try { taskProperties = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE); Settings.mergeProperties(taskProperties); } catch (IOException ex) { - log("Unable to load the dependency-check ant task.properties file.", ex, Project.MSG_WARN); + final String msg = "Unable to load the dependency-check ant task.properties file."; + if (this.failOnError) { + throw new BuildException(msg, ex); + } + log(msg, ex, Project.MSG_WARN); } finally { if (taskProperties != null) { try { diff --git a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Update.java b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Update.java index d121f21c1..5648c7c3e 100644 --- a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Update.java +++ b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Update.java @@ -18,14 +18,17 @@ package org.owasp.dependencycheck.taskdefs; import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.data.update.exception.UpdateException; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.impl.StaticLoggerBinder; /** - * An Ant task definition to execute dependency-check update. This will download the latest data from the National Vulnerability - * Database (NVD) and store a copy in the local database. + * An Ant task definition to execute dependency-check update. This will download + * the latest data from the National Vulnerability Database (NVD) and store a + * copy in the local database. * * @author Jeremy Long */ @@ -381,10 +384,11 @@ public class Update extends Purge { } /** - * Executes the update by initializing the settings, downloads the NVD XML data, and then processes the data storing it in the - * local database. + * Executes the update by initializing the settings, downloads the NVD XML + * data, and then processes the data storing it in the local database. * - * @throws BuildException thrown if a connection to the local database cannot be made. + * @throws BuildException thrown if a connection to the local database + * cannot be made. */ @Override public void execute() throws BuildException { @@ -392,9 +396,20 @@ public class Update extends Purge { Engine engine = null; try { engine = new Engine(Update.class.getClassLoader()); - engine.doUpdates(); + try { + engine.doUpdates(); + } catch (UpdateException ex) { + if (this.isFailOnError()) { + throw new BuildException(ex); + } + log(ex.getMessage(), Project.MSG_ERR); + } } catch (DatabaseException ex) { - throw new BuildException("Unable to connect to the dependency-check database; unable to update the NVD data", ex); + final String msg = "Unable to connect to the dependency-check database; unable to update the NVD data"; + if (this.isFailOnError()) { + throw new BuildException(msg, ex); + } + log(msg, Project.MSG_ERR); } finally { Settings.cleanup(true); if (engine != null) { @@ -404,8 +419,9 @@ public class Update extends Purge { } /** - * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system properties - * required to change the proxy server, port, and connection timeout. + * Takes the properties supplied and updates the dependency-check settings. + * Additionally, this sets the system properties required to change the + * proxy server, port, and connection timeout. * * @throws BuildException thrown when an invalid setting is configured. */ diff --git a/dependency-check-ant/src/site/markdown/config-purge.md b/dependency-check-ant/src/site/markdown/config-purge.md index 80c9f5d91..e7026070a 100644 --- a/dependency-check-ant/src/site/markdown/config-purge.md +++ b/dependency-check-ant/src/site/markdown/config-purge.md @@ -14,6 +14,7 @@ Configuration: dependency-check-purge Task -------------------- The following properties can be set on the dependency-check-purge task. -Property | Description | Default Value -----------------------|----------------------------------------------------------------|------------------ -dataDirectory | Data directory that is used to store the local copy of the NVD | data +Property | Description | Default Value +----------------------|------------------------------------------------------------------------|------------------ +dataDirectory | Data directory that is used to store the local copy of the NVD | data +failOnError | Whether the build should fail if there is an error executing the purge | true diff --git a/dependency-check-ant/src/site/markdown/config-update.md b/dependency-check-ant/src/site/markdown/config-update.md index dd62e3dbe..87c772b80 100644 --- a/dependency-check-ant/src/site/markdown/config-update.md +++ b/dependency-check-ant/src/site/markdown/config-update.md @@ -24,6 +24,7 @@ proxyPort | The Proxy Port. |   proxyUsername | Defines the proxy user name. |   proxyPassword | Defines the proxy password. |   connectionTimeout | The URL Connection Timeout. |   +failOnError | Whether the build should fail if there is an error executing the update | true Advanced Configuration ==================== diff --git a/dependency-check-ant/src/site/markdown/configuration.md b/dependency-check-ant/src/site/markdown/configuration.md index e2bf57a24..a711cd013 100644 --- a/dependency-check-ant/src/site/markdown/configuration.md +++ b/dependency-check-ant/src/site/markdown/configuration.md @@ -34,6 +34,7 @@ Property | Description autoUpdate | Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. | true cveValidForHours | Sets the number of hours to wait before checking for new updates from the NVD | 4 failBuildOnCVSS | Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 which means since the CVSS scores are 0-10, by default the build will never fail. | 11 +failOnError | Whether the build should fail if there is an error executing the dependency-check analysis | true projectName | The name of the project being scanned. | Dependency-Check reportFormat | The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this within the Site plugin unless the externalReport is set to true. | HTML reportOutputDirectory | The location to write the report(s). Note, this is not used if generating the report as part of a `mvn site` build | 'target' diff --git a/dependency-check-cli/pom.xml b/dependency-check-cli/pom.xml index 29689775e..b7a168914 100644 --- a/dependency-check-cli/pom.xml +++ b/dependency-check-cli/pom.xml @@ -20,7 +20,7 @@ Copyright (c) 2012 - Jeremy Long. All Rights Reserved. org.owasp dependency-check-parent - 1.4.1-SNAPSHOT + 1.4.4-SNAPSHOT dependency-check-cli diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java index c8bc71cd6..b89580df1 100644 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/App.java @@ -37,6 +37,10 @@ import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.core.FileAppender; +import org.owasp.dependencycheck.data.update.exception.UpdateException; +import org.owasp.dependencycheck.exception.ExceptionCollection; +import org.owasp.dependencycheck.exception.ReportException; +import org.owasp.dependencycheck.utils.InvalidSettingException; import org.slf4j.impl.StaticLoggerBinder; /** @@ -57,21 +61,26 @@ public class App { * @param args the command line arguments */ public static void main(String[] args) { + int exitCode = 0; try { Settings.initialize(); final App app = new App(); - app.run(args); + exitCode = app.run(args); + LOGGER.debug("Exit code: " + exitCode); } finally { Settings.cleanup(true); } + System.exit(exitCode); } /** * Main CLI entry-point into the application. * * @param args the command line arguments + * @return the exit code to return */ - public void run(String[] args) { + public int run(String[] args) { + int exitCode = 0; final CliParser cli = new CliParser(); try { @@ -79,11 +88,11 @@ public class App { } catch (FileNotFoundException ex) { System.err.println(ex.getMessage()); cli.printHelp(); - return; + return -1; } catch (ParseException ex) { System.err.println(ex.getMessage()); cli.printHelp(); - return; + return -2; } if (cli.getVerboseLog() != null) { @@ -93,8 +102,15 @@ public class App { if (cli.isPurge()) { if (cli.getConnectionString() != null) { LOGGER.error("Unable to purge the database when using a non-default connection string"); + exitCode = -3; } else { - populateSettings(cli); + try { + populateSettings(cli); + } catch (InvalidSettingException ex) { + LOGGER.error(ex.getMessage()); + LOGGER.debug("Error loading properties file", ex); + exitCode = -4; + } File db; try { db = new File(Settings.getDataDirectory(), "dc.h2.db"); @@ -103,46 +119,101 @@ public class App { LOGGER.info("Database file purged; local copy of the NVD has been removed"); } else { LOGGER.error("Unable to delete '{}'; please delete the file manually", db.getAbsolutePath()); + exitCode = -5; } } else { LOGGER.error("Unable to purge database; the database file does not exists: {}", db.getAbsolutePath()); + exitCode = -6; } } catch (IOException ex) { LOGGER.error("Unable to delete the database"); + exitCode = -7; } } } else if (cli.isGetVersion()) { cli.printVersionInfo(); } else if (cli.isUpdateOnly()) { - populateSettings(cli); - runUpdateOnly(); - } else if (cli.isRunScan()) { - populateSettings(cli); try { - runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getProjectName(), cli.getScanFiles(), - cli.getExcludeList(), cli.getSymLinkDepth()); + populateSettings(cli); + } catch (InvalidSettingException ex) { + LOGGER.error(ex.getMessage()); + LOGGER.debug("Error loading properties file", ex); + exitCode = -4; + } + try { + runUpdateOnly(); + } catch (UpdateException ex) { + LOGGER.error(ex.getMessage()); + exitCode = -8; + } catch (DatabaseException ex) { + LOGGER.error(ex.getMessage()); + exitCode = -9; + } + } else if (cli.isRunScan()) { + try { + populateSettings(cli); + } catch (InvalidSettingException ex) { + LOGGER.error(ex.getMessage()); + LOGGER.debug("Error loading properties file", ex); + exitCode = -4; + } + try { + final String[] scanFiles = cli.getScanFiles(); + if (scanFiles != null) { + runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getProjectName(), scanFiles, + cli.getExcludeList(), cli.getSymLinkDepth()); + } else { + LOGGER.error("No scan files configured"); + } } catch (InvalidScanPathException ex) { LOGGER.error("An invalid scan path was detected; unable to scan '//*' paths"); + exitCode = -10; + } catch (DatabaseException ex) { + LOGGER.error(ex.getMessage()); + exitCode = -11; + } catch (ReportException ex) { + LOGGER.error(ex.getMessage()); + exitCode = -12; + } catch (ExceptionCollection ex) { + if (ex.isFatal()) { + exitCode = -13; + LOGGER.error("One or more fatal errors occured"); + } else { + exitCode = -14; + } + for (Throwable e : ex.getExceptions()) { + LOGGER.error(e.getMessage()); + } } } else { cli.printHelp(); } + return exitCode; } /** - * Scans the specified directories and writes the dependency reports to the reportDirectory. + * Scans the specified directories and writes the dependency reports to the + * reportDirectory. * - * @param reportDirectory the path to the directory where the reports will be written + * @param reportDirectory the path to the directory where the reports will + * be written * @param outputFormat the output format of the report * @param applicationName the application name for the report * @param files the files/directories to scan * @param excludes the patterns for files/directories to exclude * @param symLinkDepth the depth that symbolic links will be followed * - * @throws InvalidScanPathException thrown if the path to scan starts with "//" + * @throws InvalidScanPathException thrown if the path to scan starts with + * "//" + * @throws ReportException thrown when the report cannot be generated + * @throws DatabaseException thrown when there is an error connecting to the + * database + * @throws ExceptionCollection thrown when an exception occurs during + * analysis; there may be multiple exceptions contained within the + * collection. */ private void runScan(String reportDirectory, String outputFormat, String applicationName, String[] files, - String[] excludes, int symLinkDepth) throws InvalidScanPathException { + String[] excludes, int symLinkDepth) throws InvalidScanPathException, DatabaseException, ExceptionCollection, ReportException { Engine engine = null; try { engine = new Engine(); @@ -174,8 +245,6 @@ public class App { include = "**/*"; } } - //LOGGER.debug("baseDir: {}", baseDir); - //LOGGER.debug("include: {}", include); scanner.setBasedir(baseDir); final String[] includes = {include}; scanner.setIncludes(includes); @@ -197,7 +266,15 @@ public class App { } engine.scan(paths); - engine.analyzeDependencies(); + ExceptionCollection exCol = null; + try { + engine.analyzeDependencies(); + } catch (ExceptionCollection ex) { + if (ex.isFatal()) { + throw ex; + } + exCol = ex; + } final List dependencies = engine.getDependencies(); DatabaseProperties prop = null; CveDB cve = null; @@ -205,8 +282,6 @@ public class App { cve = new CveDB(); cve.open(); prop = cve.getDatabaseProperties(); - } catch (DatabaseException ex) { - LOGGER.debug("Unable to retrieve DB Properties", ex); } finally { if (cve != null) { cve.close(); @@ -215,34 +290,37 @@ public class App { final ReportGenerator report = new ReportGenerator(applicationName, dependencies, engine.getAnalyzers(), prop); try { report.generateReports(reportDirectory, outputFormat); - } catch (IOException ex) { - LOGGER.error("There was an IO error while attempting to generate the report."); - LOGGER.debug("", ex); - } catch (Throwable ex) { - LOGGER.error("There was an error while attempting to generate the report."); - LOGGER.debug("", ex); + } catch (ReportException ex) { + if (exCol != null) { + exCol.addException(ex); + throw exCol; + } else { + throw ex; + } + } + if (exCol != null && exCol.getExceptions().size() > 0) { + throw exCol; } - } catch (DatabaseException ex) { - LOGGER.error("Unable to connect to the dependency-check database; analysis has stopped"); - LOGGER.debug("", ex); } finally { if (engine != null) { engine.cleanup(); } } + } /** * Only executes the update phase of dependency-check. + * + * @throws UpdateException thrown if there is an error updating + * @throws DatabaseException thrown if a fatal error occurred and a + * connection to the database could not be established */ - private void runUpdateOnly() { + private void runUpdateOnly() throws UpdateException, DatabaseException { Engine engine = null; try { engine = new Engine(); engine.doUpdates(); - } catch (DatabaseException ex) { - LOGGER.error("Unable to connect to the dependency-check database; analysis has stopped"); - LOGGER.debug("", ex); } finally { if (engine != null) { engine.cleanup(); @@ -253,11 +331,13 @@ public class App { /** * Updates the global Settings. * - * @param cli a reference to the CLI Parser that contains the command line arguments used to set the corresponding settings in - * the core engine. + * @param cli a reference to the CLI Parser that contains the command line + * arguments used to set the corresponding settings in the core engine. + * + * @throws InvalidSettingException thrown when a user defined properties + * file is unable to be loaded. */ - private void populateSettings(CliParser cli) { - + private void populateSettings(CliParser cli) throws InvalidSettingException { final boolean autoUpdate = cli.isAutoUpdate(); final String connectionTimeout = cli.getConnectionTimeout(); final String proxyServer = cli.getProxyServer(); @@ -286,11 +366,9 @@ public class App { try { Settings.mergeProperties(propertiesFile); } catch (FileNotFoundException ex) { - LOGGER.error("Unable to load properties file '{}'", propertiesFile.getPath()); - LOGGER.debug("", ex); + throw new InvalidSettingException("Unable to find properties file '" + propertiesFile.getPath() + "'", ex); } catch (IOException ex) { - LOGGER.error("Unable to find properties file '{}'", propertiesFile.getPath()); - LOGGER.debug("", ex); + throw new InvalidSettingException("Error reading properties file '" + propertiesFile.getPath() + "'", ex); } } // We have to wait until we've merged the properties before attempting to set whether we use @@ -385,15 +463,16 @@ public class App { } /** - * Takes a path and resolves it to be a canonical & absolute path. The caveats are that this method will take an Ant style - * file selector path (../someDir/**\/*.jar) and convert it to an absolute/canonical path (at least to the left of the first * - * or ?). + * Takes a path and resolves it to be a canonical & absolute path. The + * caveats are that this method will take an Ant style file selector path + * (../someDir/**\/*.jar) and convert it to an absolute/canonical path (at + * least to the left of the first * or ?). * * @param path the path to canonicalize * @return the canonical path */ protected String ensureCanonicalPath(String path) { - String basePath = null; + String basePath; String wildCards = null; final String file = path.replace('\\', '/'); if (file.contains("*") || file.contains("?")) { diff --git a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java index d2e522834..df75602a1 100644 --- a/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java +++ b/dependency-check-cli/src/main/java/org/owasp/dependencycheck/CliParser.java @@ -196,6 +196,10 @@ public final class CliParser { isValid = false; final String msg = String.format("Invalid '%s' argument: '%s'%nUnable to scan paths that start with '//'.", argumentName, path); throw new FileNotFoundException(msg); + } else if ((path.endsWith("/*") && !path.endsWith("**/*")) || (path.endsWith("\\*") && path.endsWith("**\\*"))) { + final String msg = String.format("Possibly incorrect path '%s' from argument '%s' because it ends with a slash star; " + + "dependency-check uses ant-style paths", path, argumentName); + LOGGER.warn(msg); } } @@ -966,7 +970,7 @@ public final class CliParser { */ public void printVersionInfo() { final String version = String.format("%s version %s", - Settings.getString(Settings.KEYS.APPLICATION_VAME, "dependency-check"), + Settings.getString(Settings.KEYS.APPLICATION_NAME, "dependency-check"), Settings.getString(Settings.KEYS.APPLICATION_VERSION, "Unknown")); System.out.println(version); } diff --git a/dependency-check-core/pom.xml b/dependency-check-core/pom.xml index 387601cdd..a1bc2ae0c 100644 --- a/dependency-check-core/pom.xml +++ b/dependency-check-core/pom.xml @@ -20,7 +20,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. org.owasp dependency-check-parent - 1.4.1-SNAPSHOT + 1.4.4-SNAPSHOT dependency-check-core diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java index c35a796e0..473ba3faf 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java @@ -30,6 +30,8 @@ import org.owasp.dependencycheck.data.update.UpdateService; import org.owasp.dependencycheck.data.update.exception.UpdateException; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.exception.NoDataException; +import org.owasp.dependencycheck.exception.ExceptionCollection; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.InvalidSettingException; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; @@ -47,8 +49,10 @@ import java.util.Map; import java.util.Set; /** - * Scans files, directories, etc. for Dependencies. Analyzers are loaded and used to process the files found by the scan, if a - * file is encountered and an Analyzer is associated with the file type then the file is turned into a dependency. + * Scans files, directories, etc. for Dependencies. Analyzers are loaded and + * used to process the files found by the scan, if a file is encountered and an + * Analyzer is associated with the file type then the file is turned into a + * dependency. * * @author Jeremy Long */ @@ -61,15 +65,16 @@ public class Engine implements FileFilter { /** * A Map of analyzers grouped by Analysis phase. */ - private Map> analyzers = new EnumMap>(AnalysisPhase.class); + private final Map> analyzers = new EnumMap>(AnalysisPhase.class); /** * A Map of analyzers grouped by Analysis phase. */ - private Set fileTypeAnalyzers = new HashSet(); + private final Set fileTypeAnalyzers = new HashSet(); /** - * The ClassLoader to use when dynamically loading Analyzer and Update services. + * The ClassLoader to use when dynamically loading Analyzer and Update + * services. */ private ClassLoader serviceClassLoader = Thread.currentThread().getContextClassLoader(); /** @@ -80,7 +85,8 @@ public class Engine implements FileFilter { /** * Creates a new Engine. * - * @throws DatabaseException thrown if there is an error connecting to the database + * @throws DatabaseException thrown if there is an error connecting to the + * database */ public Engine() throws DatabaseException { initializeEngine(); @@ -90,7 +96,8 @@ public class Engine implements FileFilter { * Creates a new Engine. * * @param serviceClassLoader a reference the class loader being used - * @throws DatabaseException thrown if there is an error connecting to the database + * @throws DatabaseException thrown if there is an error connecting to the + * database */ public Engine(ClassLoader serviceClassLoader) throws DatabaseException { this.serviceClassLoader = serviceClassLoader; @@ -98,9 +105,11 @@ public class Engine implements FileFilter { } /** - * Creates a new Engine using the specified classloader to dynamically load Analyzer and Update services. + * Creates a new Engine using the specified classloader to dynamically load + * Analyzer and Update services. * - * @throws DatabaseException thrown if there is an error connecting to the database + * @throws DatabaseException thrown if there is an error connecting to the + * database */ protected final void initializeEngine() throws DatabaseException { ConnectionFactory.initialize(); @@ -115,7 +124,8 @@ public class Engine implements FileFilter { } /** - * Loads the analyzers specified in the configuration file (or system properties). + * Loads the analyzers specified in the configuration file (or system + * properties). */ private void loadAnalyzers() { if (!analyzers.isEmpty()) { @@ -164,8 +174,9 @@ public class Engine implements FileFilter { } /** - * Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies - * identified are added to the dependency collection. + * Scans an array of files or directories. If a directory is specified, it + * will be scanned recursively. Any dependencies identified are added to the + * dependency collection. * * @param paths an array of paths to files or directories to be analyzed * @return the list of dependencies scanned @@ -183,8 +194,9 @@ public class Engine implements FileFilter { } /** - * Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies identified - * are added to the dependency collection. + * Scans a given file or directory. If a directory is specified, it will be + * scanned recursively. Any dependencies identified are added to the + * dependency collection. * * @param path the path to a file or directory to be analyzed * @return the list of dependencies scanned @@ -195,8 +207,9 @@ public class Engine implements FileFilter { } /** - * Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies - * identified are added to the dependency collection. + * Scans an array of files or directories. If a directory is specified, it + * will be scanned recursively. Any dependencies identified are added to the + * dependency collection. * * @param files an array of paths to files or directories to be analyzed. * @return the list of dependencies @@ -214,8 +227,9 @@ public class Engine implements FileFilter { } /** - * Scans a collection of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies - * identified are added to the dependency collection. + * Scans a collection of files or directories. If a directory is specified, + * it will be scanned recursively. Any dependencies identified are added to + * the dependency collection. * * @param files a set of paths to files or directories to be analyzed * @return the list of dependencies scanned @@ -233,8 +247,9 @@ public class Engine implements FileFilter { } /** - * Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies identified - * are added to the dependency collection. + * Scans a given file or directory. If a directory is specified, it will be + * scanned recursively. Any dependencies identified are added to the + * dependency collection. * * @param file the path to a file or directory to be analyzed * @return the list of dependencies scanned @@ -257,7 +272,8 @@ public class Engine implements FileFilter { } /** - * Recursively scans files and directories. Any dependencies identified are added to the dependency collection. + * Recursively scans files and directories. Any dependencies identified are + * added to the dependency collection. * * @param dir the directory to scan * @return the list of Dependency objects scanned @@ -282,7 +298,8 @@ public class Engine implements FileFilter { } /** - * Scans a specified file. If a dependency is identified it is added to the dependency collection. + * Scans a specified file. If a dependency is identified it is added to the + * dependency collection. * * @param file The file to scan * @return the scanned dependency @@ -301,20 +318,38 @@ public class Engine implements FileFilter { } /** - * Runs the analyzers against all of the dependencies. Since the mutable dependencies list is exposed via - * {@link #getDependencies()}, this method iterates over a copy of the dependencies list. Thus, the potential for - * {@link java.util.ConcurrentModificationException}s is avoided, and analyzers may safely add or remove entries from the - * dependencies list. + * Runs the analyzers against all of the dependencies. Since the mutable + * dependencies list is exposed via {@link #getDependencies()}, this method + * iterates over a copy of the dependencies list. Thus, the potential for + * {@link java.util.ConcurrentModificationException}s is avoided, and + * analyzers may safely add or remove entries from the dependencies list. + * + * Every effort is made to complete analysis on the dependencies. In some + * cases an exception will occur with part of the analysis being performed + * which may not affect the entire analysis. If an exception occurs it will + * be included in the thrown exception collection. + * + * @throws ExceptionCollection a collections of any exceptions that occurred + * during analysis */ - public void analyzeDependencies() { + public void analyzeDependencies() throws ExceptionCollection { + final List exceptions = new ArrayList(); boolean autoUpdate = true; try { autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE); } catch (InvalidSettingException ex) { LOGGER.debug("Invalid setting for auto-update; using true."); + exceptions.add(ex); } if (autoUpdate) { - doUpdates(); + try { + doUpdates(); + } catch (UpdateException ex) { + exceptions.add(ex); + LOGGER.warn("Unable to update Cached Web DataSource, using local " + + "data instead. Results may not include recent vulnerabilities."); + LOGGER.debug("Update Error", ex); + } } //need to ensure that data exists @@ -323,16 +358,17 @@ public class Engine implements FileFilter { } catch (NoDataException ex) { LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage()); LOGGER.debug("", ex); - return; + exceptions.add(ex); + throw new ExceptionCollection("Unable to continue dependency-check analysis.", exceptions, true); } catch (DatabaseException ex) { LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage()); LOGGER.debug("", ex); - return; - + exceptions.add(ex); + throw new ExceptionCollection("Unable to connect to the dependency-check database", exceptions, true); } LOGGER.debug("\n----------------------------------------------------\nBEGIN ANALYSIS\n----------------------------------------------------"); - LOGGER.info("Analysis Starting"); + LOGGER.info("Analysis Started"); final long analysisStart = System.currentTimeMillis(); // analysis phases @@ -340,7 +376,12 @@ public class Engine implements FileFilter { final List analyzerList = analyzers.get(phase); for (Analyzer a : analyzerList) { - a = initializeAnalyzer(a); + try { + a = initializeAnalyzer(a); + } catch (InitializationException ex) { + exceptions.add(ex); + continue; + } /* need to create a copy of the collection because some of the * analyzers may modify it. This prevents ConcurrentModificationExceptions. @@ -361,10 +402,12 @@ public class Engine implements FileFilter { } catch (AnalysisException ex) { LOGGER.warn("An error occurred while analyzing '{}'.", d.getActualFilePath()); LOGGER.debug("", ex); + exceptions.add(ex); } catch (Throwable ex) { //final AnalysisException ax = new AnalysisException(axMsg, ex); LOGGER.warn("An unexpected error occurred during analysis of '{}'", d.getActualFilePath()); LOGGER.debug("", ex); + exceptions.add(ex); } } } @@ -380,6 +423,9 @@ public class Engine implements FileFilter { LOGGER.debug("\n----------------------------------------------------\nEND ANALYSIS\n----------------------------------------------------"); LOGGER.info("Analysis Complete ({} ms)", System.currentTimeMillis() - analysisStart); + if (exceptions.size() > 0) { + throw new ExceptionCollection("One or more exceptions occured during dependency-check analysis", exceptions); + } } /** @@ -387,12 +433,14 @@ public class Engine implements FileFilter { * * @param analyzer the analyzer to initialize * @return the initialized analyzer + * @throws InitializationException thrown when there is a problem + * initializing the analyzer */ - protected Analyzer initializeAnalyzer(Analyzer analyzer) { + protected Analyzer initializeAnalyzer(Analyzer analyzer) throws InitializationException { try { LOGGER.debug("Initializing {}", analyzer.getName()); analyzer.initialize(); - } catch (Throwable ex) { + } catch (InitializationException ex) { LOGGER.error("Exception occurred initializing {}.", analyzer.getName()); LOGGER.debug("", ex); try { @@ -400,6 +448,16 @@ public class Engine implements FileFilter { } catch (Throwable ex1) { LOGGER.trace("", ex1); } + throw ex; + } catch (Throwable ex) { + LOGGER.error("Unexpected exception occurred initializing {}.", analyzer.getName()); + LOGGER.debug("", ex); + try { + analyzer.close(); + } catch (Throwable ex1) { + LOGGER.trace("", ex1); + } + throw new InitializationException("Unexpected Exception", ex); } return analyzer; } @@ -419,28 +477,26 @@ public class Engine implements FileFilter { } /** - * Cycles through the cached web data sources and calls update on all of them. + * Cycles through the cached web data sources and calls update on all of + * them. + * + * @throws UpdateException thrown if the operation fails */ - public void doUpdates() { + public void doUpdates() throws UpdateException { LOGGER.info("Checking for updates"); final long updateStart = System.currentTimeMillis(); final UpdateService service = new UpdateService(serviceClassLoader); final Iterator iterator = service.getDataSources(); while (iterator.hasNext()) { final CachedWebDataSource source = iterator.next(); - try { - source.update(); - } catch (UpdateException ex) { - LOGGER.warn( - "Unable to update Cached Web DataSource, using local data instead. Results may not include recent vulnerabilities."); - LOGGER.debug("Unable to update details for {}", source.getClass().getName(), ex); - } + source.update(); } LOGGER.info("Check for updates complete ({} ms)", System.currentTimeMillis() - updateStart); } /** - * Returns a full list of all of the analyzers. This is useful for reporting which analyzers where used. + * Returns a full list of all of the analyzers. This is useful for reporting + * which analyzers where used. * * @return a list of Analyzers */ @@ -457,7 +513,8 @@ public class Engine implements FileFilter { * Checks all analyzers to see if an extension is supported. * * @param file a file extension - * @return true or false depending on whether or not the file extension is supported + * @return true or false depending on whether or not the file extension is + * supported */ @Override public boolean accept(File file) { @@ -483,10 +540,12 @@ public class Engine implements FileFilter { } /** - * Checks the CPE Index to ensure documents exists. If none exist a NoDataException is thrown. + * Checks the CPE Index to ensure documents exists. If none exist a + * NoDataException is thrown. * * @throws NoDataException thrown if no data exists in the CPE Index - * @throws DatabaseException thrown if there is an exception opening the database + * @throws DatabaseException thrown if there is an exception opening the + * database */ private void ensureDataExists() throws NoDataException, DatabaseException { final CveDB cve = new CveDB(); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/agent/DependencyCheckScanAgent.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/agent/DependencyCheckScanAgent.java index b2b63955e..56ab93810 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/agent/DependencyCheckScanAgent.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/agent/DependencyCheckScanAgent.java @@ -27,6 +27,7 @@ import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.exception.ExceptionCollection; import org.owasp.dependencycheck.exception.ScanAgentException; import org.owasp.dependencycheck.reporting.ReportGenerator; import org.owasp.dependencycheck.utils.Settings; @@ -34,10 +35,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This class provides a way to easily conduct a scan solely based on existing evidence metadata rather than collecting evidence - * from the files themselves. This class is based on the Ant task and Maven plugin with the exception that it takes a list of - * dependencies that can be programmatically added from data in a spreadsheet, database or some other datasource and conduct a - * scan based on this pre-defined evidence. + * This class provides a way to easily conduct a scan solely based on existing + * evidence metadata rather than collecting evidence from the files themselves. + * This class is based on the Ant task and Maven plugin with the exception that + * it takes a list of dependencies that can be programmatically added from data + * in a spreadsheet, database or some other datasource and conduct a scan based + * on this pre-defined evidence. * *

Example:

*
@@ -138,7 +141,8 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * Specifies the destination directory for the generated Dependency-Check report.
+     * Specifies the destination directory for the generated Dependency-Check
+     * report.
      */
     private String reportOutputDirectory;
 
@@ -161,9 +165,11 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 which
-     * means since the CVSS scores are 0-10, by default the build will never fail and the CVSS score is set to 11. The valid range
-     * for the fail build on CVSS is 0 to 11, where anything above 10 will not cause the build to fail.
+     * Specifies if the build should be failed if a CVSS score above a specified
+     * level is identified. The default is 11 which means since the CVSS scores
+     * are 0-10, by default the build will never fail and the CVSS score is set
+     * to 11. The valid range for the fail build on CVSS is 0 to 11, where
+     * anything above 10 will not cause the build to fail.
      */
     private float failBuildOnCVSS = 11;
 
@@ -186,8 +192,8 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. Default
-     * is true.
+     * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not
+     * recommended that this be turned to false. Default is true.
      */
     private boolean autoUpdate = true;
 
@@ -233,8 +239,9 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this within the
-     * Site plugin unless the externalReport is set to true. Default is HTML.
+     * The report format to be generated (HTML, XML, VULN, ALL). This
+     * configuration option has no affect if using this within the Site plugin
+     * unless the externalReport is set to true. Default is HTML.
      */
     private ReportGenerator.Format reportFormat = ReportGenerator.Format.HTML;
 
@@ -283,7 +290,9 @@ public class DependencyCheckScanAgent {
      * Get the value of proxyServer.
      *
      * @return the value of proxyServer
-     * @deprecated use {@link org.owasp.dependencycheck.agent.DependencyCheckScanAgent#getProxyServer()} instead
+     * @deprecated use
+     * {@link org.owasp.dependencycheck.agent.DependencyCheckScanAgent#getProxyServer()}
+     * instead
      */
     @Deprecated
     public String getProxyUrl() {
@@ -694,8 +703,8 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * Additional ZIP File extensions to add analyze. This should be a comma-separated list of file extensions to treat like ZIP
-     * files.
+     * Additional ZIP File extensions to add analyze. This should be a
+     * comma-separated list of file extensions to treat like ZIP files.
      */
     private String zipExtensions;
 
@@ -836,11 +845,17 @@ public class DependencyCheckScanAgent {
      * Executes the Dependency-Check on the dependent libraries.
      *
      * @return the Engine used to scan the dependencies.
-     * @throws org.owasp.dependencycheck.data.nvdcve.DatabaseException thrown if there is an exception connecting to the database
+     * @throws ExceptionCollection a collection of one or more exceptions that
+     * occurred during analysis.
      */
-    private Engine executeDependencyCheck() throws DatabaseException {
+    private Engine executeDependencyCheck() throws ExceptionCollection {
         populateSettings();
-        final Engine engine = new Engine();
+        final Engine engine;
+        try {
+            engine = new Engine();
+        } catch (DatabaseException ex) {
+            throw new ExceptionCollection(ex, true);
+        }
         engine.setDependencies(this.dependencies);
         engine.analyzeDependencies();
         return engine;
@@ -881,8 +896,9 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system properties
-     * required to change the proxy server, port, and connection timeout.
+     * Takes the properties supplied and updates the dependency-check settings.
+     * Additionally, this sets the system properties required to change the
+     * proxy server, port, and connection timeout.
      */
     private void populateSettings() {
         Settings.initialize();
@@ -925,7 +941,8 @@ public class DependencyCheckScanAgent {
      * Executes the dependency-check and generates the report.
      *
      * @return a reference to the engine used to perform the scan.
-     * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if there is an exception executing the scan.
+     * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if
+     * there is an exception executing the scan.
      */
     public Engine execute() throws ScanAgentException {
         Engine engine = null;
@@ -940,10 +957,12 @@ public class DependencyCheckScanAgent {
             if (this.failBuildOnCVSS <= 10) {
                 checkForFailure(engine.getDependencies());
             }
-        } catch (DatabaseException ex) {
-            LOGGER.error(
-                    "Unable to connect to the dependency-check database; analysis has stopped");
-            LOGGER.debug("", ex);
+        } catch (ExceptionCollection ex) {
+            if (ex.isFatal()) {
+                LOGGER.error("A fatal exception occurred during analysis; analysis has stopped. Please see the debug log for more details.");
+                LOGGER.debug("", ex);
+            }
+            throw new ScanAgentException("One or more exceptions occurred during analysis; please see the debug log for more details.", ex);
         } finally {
             Settings.cleanup(true);
             if (engine != null) {
@@ -954,11 +973,12 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * Checks to see if a vulnerability has been identified with a CVSS score that is above the threshold set in the
-     * configuration.
+     * Checks to see if a vulnerability has been identified with a CVSS score
+     * that is above the threshold set in the configuration.
      *
      * @param dependencies the list of dependency objects
-     * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if there is an exception executing the scan.
+     * @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if
+     * there is an exception executing the scan.
      */
     private void checkForFailure(List dependencies) throws ScanAgentException {
         final StringBuilder ids = new StringBuilder();
@@ -986,7 +1006,8 @@ public class DependencyCheckScanAgent {
     }
 
     /**
-     * Generates a warning message listing a summary of dependencies and their associated CPE and CVE entries.
+     * Generates a warning message listing a summary of dependencies and their
+     * associated CPE and CVE entries.
      *
      * @param dependencies a list of dependency objects
      */
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractAnalyzer.java
index 67a9a85a5..0b35e7fb6 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractAnalyzer.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractAnalyzer.java
@@ -17,7 +17,11 @@
  */
 package org.owasp.dependencycheck.analyzer;
 
+import org.owasp.dependencycheck.exception.InitializationException;
+
 /**
+ * Base class for analyzers to avoid code duplication of initialize and close
+ * as most analyzers do not need these methods.
  *
  * @author Jeremy Long
  */
@@ -26,10 +30,10 @@ public abstract class AbstractAnalyzer implements Analyzer {
     /**
      * The initialize method does nothing for this Analyzer.
      *
-     * @throws Exception thrown if there is an exception
+     * @throws InitializationException thrown if there is an exception
      */
     @Override
-    public void initialize() throws Exception {
+    public void initialize() throws InitializationException {
         //do nothing
     }
 
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractFileTypeAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractFileTypeAnalyzer.java
index 10ef1d402..628418f3c 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractFileTypeAnalyzer.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractFileTypeAnalyzer.java
@@ -30,9 +30,11 @@ import java.io.FileFilter;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import org.owasp.dependencycheck.exception.InitializationException;
 
 /**
- * The base FileTypeAnalyzer that all analyzers that have specific file types they analyze should extend.
+ * The base FileTypeAnalyzer that all analyzers that have specific file types
+ * they analyze should extend.
  *
  * @author Jeremy Long
  */
@@ -40,7 +42,8 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen
 
     //
     /**
-     * Base constructor that all children must call. This checks the configuration to determine if the analyzer is enabled.
+     * Base constructor that all children must call. This checks the
+     * configuration to determine if the analyzer is enabled.
      */
     public AbstractFileTypeAnalyzer() {
         reset();
@@ -58,7 +61,8 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen
     private boolean filesMatched = false;
 
     /**
-     * Get the value of filesMatched. A flag indicating whether the scan included any file types this analyzer supports.
+     * Get the value of filesMatched. A flag indicating whether the scan
+     * included any file types this analyzer supports.
      *
      * @return the value of filesMatched
      */
@@ -67,7 +71,8 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen
     }
 
     /**
-     * Set the value of filesMatched. A flag indicating whether the scan included any file types this analyzer supports.
+     * Set the value of filesMatched. A flag indicating whether the scan
+     * included any file types this analyzer supports.
      *
      * @param filesMatched new value of filesMatched
      */
@@ -102,11 +107,13 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen
     //
     /**
      * 

- * Returns the {@link java.io.FileFilter} used to determine which files are to be analyzed. An example would be an analyzer - * that inspected Java jar files. Implementors may use {@link org.owasp.dependencycheck.utils.FileFilterBuilder}.

+ * Returns the {@link java.io.FileFilter} used to determine which files are + * to be analyzed. An example would be an analyzer that inspected Java jar + * files. Implementors may use + * {@link org.owasp.dependencycheck.utils.FileFilterBuilder}.

*

- * If the analyzer returns null it will not cause additional files to be analyzed, but will be executed against every file - * loaded.

+ * If the analyzer returns null it will not cause additional files to be + * analyzed, but will be executed against every file loaded.

* * @return the file filter used to determine which files are to be analyzed */ @@ -115,13 +122,15 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen /** * Initializes the file type analyzer. * - * @throws Exception thrown if there is an exception during initialization + * @throws InitializationException thrown if there is an exception during + * initialization */ - protected abstract void initializeFileTypeAnalyzer() throws Exception; + protected abstract void initializeFileTypeAnalyzer() throws InitializationException; /** - * Analyzes a given dependency. If the dependency is an archive, such as a WAR or EAR, the contents are extracted, scanned, - * and added to the list of dependencies within the engine. + * Analyzes a given dependency. If the dependency is an archive, such as a + * WAR or EAR, the contents are extracted, scanned, and added to the list of + * dependencies within the engine. * * @param dependency the dependency to analyze * @param engine the engine scanning @@ -142,10 +151,11 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen /** * Initializes the analyzer. * - * @throws Exception thrown if there is an exception during initialization + * @throws InitializationException thrown if there is an exception during + * initialization */ @Override - public final void initialize() throws Exception { + public final void initialize() throws InitializationException { if (filesMatched) { initializeFileTypeAnalyzer(); } else { @@ -169,8 +179,9 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen } /** - * Analyzes a given dependency. If the dependency is an archive, such as a WAR or EAR, the contents are extracted, scanned, - * and added to the list of dependencies within the engine. + * Analyzes a given dependency. If the dependency is an archive, such as a + * WAR or EAR, the contents are extracted, scanned, and added to the list of + * dependencies within the engine. * * @param dependency the dependency to analyze * @param engine the engine scanning @@ -202,8 +213,8 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen // /** *

- * Utility method to help in the creation of the extensions set. This constructs a new Set that can be used in a final static - * declaration.

+ * Utility method to help in the creation of the extensions set. This + * constructs a new Set that can be used in a final static declaration.

*

* This implementation was copied from * http://stackoverflow.com/questions/2041778/initialize-java-hashset-values-by-construction

diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzer.java index 812f70394..4667d5f78 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzer.java @@ -25,9 +25,10 @@ import java.net.URL; import java.util.List; import java.util.Set; import java.util.regex.Pattern; -import org.owasp.dependencycheck.suppression.SuppressionParseException; -import org.owasp.dependencycheck.suppression.SuppressionParser; -import org.owasp.dependencycheck.suppression.SuppressionRule; +import org.owasp.dependencycheck.exception.InitializationException; +import org.owasp.dependencycheck.xml.suppression.SuppressionParseException; +import org.owasp.dependencycheck.xml.suppression.SuppressionParser; +import org.owasp.dependencycheck.xml.suppression.SuppressionRule; import org.owasp.dependencycheck.utils.DownloadFailedException; import org.owasp.dependencycheck.utils.Downloader; import org.owasp.dependencycheck.utils.FileUtils; @@ -63,12 +64,16 @@ public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer { /** * The initialize method loads the suppression XML file. * - * @throws Exception thrown if there is an exception + * @throws InitializationException thrown if there is an exception */ @Override - public void initialize() throws Exception { + public void initialize() throws InitializationException { super.initialize(); - loadSuppressionData(); + try { + loadSuppressionData(); + } catch (SuppressionParseException ex) { + throw new InitializationException("Error initializing the suppression analyzer", ex); + } } /** @@ -104,12 +109,8 @@ public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer { File file = null; try { rules = parser.parseSuppressionRules(this.getClass().getClassLoader().getResourceAsStream("dependencycheck-base-suppression.xml")); - } catch (SuppressionParseException ex) { - LOGGER.error("Unable to parse the base suppression data file"); - LOGGER.debug("Unable to parse the base suppression data file", ex); } catch (SAXException ex) { - LOGGER.error("Unable to parse the base suppression data file"); - LOGGER.debug("Unable to parse the base suppression data file", ex); + throw new SuppressionParseException("Unable to parse the base suppression data file", ex); } final String suppressionFilePath = Settings.getString(Settings.KEYS.SUPPRESSION_FILE); if (suppressionFilePath == null) { @@ -129,29 +130,37 @@ public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer { } } else { file = new File(suppressionFilePath); + InputStream suppressionsFromClasspath = null; if (!file.exists()) { - final InputStream suppressionsFromClasspath = this.getClass().getClassLoader().getResourceAsStream(suppressionFilePath); - if (suppressionsFromClasspath != null) { - deleteTempFile = true; - file = FileUtils.getTempFile("suppression", "xml"); - try { - org.apache.commons.io.FileUtils.copyInputStreamToFile(suppressionsFromClasspath, file); - } catch (IOException ex) { - throwSuppressionParseException("Unable to locate suppressions file in classpath", ex); + try { + suppressionsFromClasspath = this.getClass().getClassLoader().getResourceAsStream(suppressionFilePath); + if (suppressionsFromClasspath != null) { + deleteTempFile = true; + file = FileUtils.getTempFile("suppression", "xml"); + try { + org.apache.commons.io.FileUtils.copyInputStreamToFile(suppressionsFromClasspath, file); + } catch (IOException ex) { + throwSuppressionParseException("Unable to locate suppressions file in classpath", ex); + } + } + } finally { + if (suppressionsFromClasspath != null) { + try { + suppressionsFromClasspath.close(); + } catch (IOException ex) { + LOGGER.debug("Failed to close stream", ex); + } } } } } - if (file != null) { try { - //rules = parser.parseSuppressionRules(file); rules.addAll(parser.parseSuppressionRules(file)); LOGGER.debug("{} suppression rules were loaded.", rules.size()); } catch (SuppressionParseException ex) { LOGGER.warn("Unable to parse suppression xml file '{}'", file.getPath()); LOGGER.warn(ex.getMessage()); - LOGGER.debug("", ex); throw ex; } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/Analyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/Analyzer.java index de4e28563..f9681fb6e 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/Analyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/Analyzer.java @@ -20,24 +20,28 @@ package org.owasp.dependencycheck.analyzer; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.exception.InitializationException; /** - * An interface that defines an Analyzer that is used to identify Dependencies. An analyzer will collect information - * about the dependency in the form of Evidence. + * An interface that defines an Analyzer that is used to identify Dependencies. + * An analyzer will collect information about the dependency in the form of + * Evidence. * * @author Jeremy Long */ public interface Analyzer { /** - * Analyzes the given dependency. The analysis could be anything from identifying an Identifier for the dependency, - * to finding vulnerabilities, etc. Additionally, if the analyzer collects enough information to add a description - * or license information for the dependency it should be added. + * Analyzes the given dependency. The analysis could be anything from + * identifying an Identifier for the dependency, to finding vulnerabilities, + * etc. Additionally, if the analyzer collects enough information to add a + * description or license information for the dependency it should be added. * * @param dependency a dependency to analyze. - * @param engine the engine that is scanning the dependencies - this is useful if we need to check other - * dependencies - * @throws AnalysisException is thrown if there is an error analyzing the dependency file + * @param engine the engine that is scanning the dependencies - this is + * useful if we need to check other dependencies + * @throws AnalysisException is thrown if there is an error analyzing the + * dependency file */ void analyze(Dependency dependency, Engine engine) throws AnalysisException; @@ -56,14 +60,17 @@ public interface Analyzer { AnalysisPhase getAnalysisPhase(); /** - * The initialize method is called (once) prior to the analyze method being called on all of the dependencies. + * The initialize method is called (once) prior to the analyze method being + * called on all of the dependencies. * - * @throws Exception is thrown if an exception occurs initializing the analyzer. + * @throws InitializationException is thrown if an exception occurs + * initializing the analyzer. */ - void initialize() throws Exception; + void initialize() throws InitializationException; /** - * The close method is called after all of the dependencies have been analyzed. + * The close method is called after all of the dependencies have been + * analyzed. * * @throws Exception is thrown if an exception occurs closing the analyzer. */ diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java index c15dc732a..e8110eede 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ArchiveAnalyzer.java @@ -49,6 +49,7 @@ import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.analyzer.exception.ArchiveExtractionException; import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.FileFilterBuilder; import org.owasp.dependencycheck.utils.FileUtils; import org.owasp.dependencycheck.utils.Settings; @@ -174,20 +175,27 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { /** * The initialize method does nothing for this Analyzer. * - * @throws Exception is thrown if there is an exception deleting or creating - * temporary files + * @throws InitializationException is thrown if there is an exception + * deleting or creating temporary files */ @Override - public void initializeFileTypeAnalyzer() throws Exception { - final File baseDir = Settings.getTempDirectory(); - tempFileLocation = File.createTempFile("check", "tmp", baseDir); - if (!tempFileLocation.delete()) { - final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath()); - throw new AnalysisException(msg); - } - if (!tempFileLocation.mkdirs()) { - final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath()); - throw new AnalysisException(msg); + public void initializeFileTypeAnalyzer() throws InitializationException { + try { + final File baseDir = Settings.getTempDirectory(); + tempFileLocation = File.createTempFile("check", "tmp", baseDir); + if (!tempFileLocation.delete()) { + setEnabled(false); + final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath()); + throw new InitializationException(msg); + } + if (!tempFileLocation.mkdirs()) { + setEnabled(false); + final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath()); + throw new InitializationException(msg); + } + } catch (IOException ex) { + setEnabled(false); + throw new InitializationException("Unable to create a temporary file", ex); } } @@ -349,6 +357,12 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { */ private void extractFiles(File archive, File destination, Engine engine) throws AnalysisException { if (archive != null && destination != null) { + String archiveExt = FileUtils.getFileExtension(archive.getName()); + if (archiveExt == null) { + return; + } + archiveExt = archiveExt.toLowerCase(); + FileInputStream fis; try { fis = new FileInputStream(archive); @@ -356,10 +370,9 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { LOGGER.debug("", ex); throw new AnalysisException("Archive file was not found.", ex); } - final String archiveExt = FileUtils.getFileExtension(archive.getName()).toLowerCase(); try { if (ZIPPABLES.contains(archiveExt)) { - BufferedInputStream in = new BufferedInputStream(fis); + final BufferedInputStream in = new BufferedInputStream(fis); ensureReadableJar(archiveExt, in); extractArchive(new ZipArchiveInputStream(in), destination, engine); } else if ("tar".equals(archiveExt)) { @@ -405,9 +418,10 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { private void ensureReadableJar(final String archiveExt, BufferedInputStream in) throws IOException { if ("jar".equals(archiveExt) && in.markSupported()) { in.mark(7); - byte[] b = new byte[7]; - in.read(b); - if (b[0] == '#' + final byte[] b = new byte[7]; + final int read = in.read(b); + if (read == 7 + && b[0] == '#' && b[1] == '!' && b[2] == '/' && b[3] == 'b' @@ -433,6 +447,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer { } } } + } else { + in.reset(); } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzer.java index 91488e9b3..9501da8e6 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzer.java @@ -43,9 +43,13 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.util.ArrayList; import java.util.List; +import javax.xml.parsers.ParserConfigurationException; +import org.owasp.dependencycheck.exception.InitializationException; +import org.apache.commons.lang3.SystemUtils; /** - * Analyzer for getting company, product, and version information from a .NET assembly. + * Analyzer for getting company, product, and version information from a .NET + * assembly. * * @author colezlaw * @@ -82,18 +86,19 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { * * @return the list of arguments to begin populating the ProcessBuilder */ - private List buildArgumentList() { + protected List buildArgumentList() { // Use file.separator as a wild guess as to whether this is Windows final List args = new ArrayList(); - if (!"\\".equals(System.getProperty("file.separator"))) { + if (!SystemUtils.IS_OS_WINDOWS) { if (Settings.getString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH) != null) { args.add(Settings.getString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH)); - } else { + } else if (isInPath("mono")) { args.add("mono"); + } else { + return null; } } args.add(grokAssemblyExe.getPath()); - return args; } @@ -113,6 +118,10 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { } final List args = buildArgumentList(); + if (args == null) { + LOGGER.warn("Assembly Analyzer was unable to execute"); + return; + } args.add(dependency.getActualFilePath()); final ProcessBuilder pb = new ProcessBuilder(args); Document doc = null; @@ -178,13 +187,20 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Initialize the analyzer. In this case, extract GrokAssembly.exe to a temporary location. + * Initialize the analyzer. In this case, extract GrokAssembly.exe to a + * temporary location. * - * @throws Exception if anything goes wrong + * @throws InitializationException thrown if anything goes wrong */ @Override - public void initializeFileTypeAnalyzer() throws Exception { - final File tempFile = File.createTempFile("GKA", ".exe", Settings.getTempDirectory()); + public void initializeFileTypeAnalyzer() throws InitializationException { + final File tempFile; + try { + tempFile = File.createTempFile("GKA", ".exe", Settings.getTempDirectory()); + } catch (IOException ex) { + setEnabled(false); + throw new InitializationException("Unable to create temporary file for the assembly analyzerr", ex); + } FileOutputStream fos = null; InputStream is = null; try { @@ -193,13 +209,11 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { IOUtils.copy(is, fos); grokAssemblyExe = tempFile; - // Set the temp file to get deleted when we're done - grokAssemblyExe.deleteOnExit(); LOGGER.debug("Extracted GrokAssembly.exe to {}", grokAssemblyExe.getPath()); } catch (IOException ioe) { this.setEnabled(false); LOGGER.warn("Could not extract GrokAssembly.exe: {}", ioe.getMessage()); - throw new AnalysisException("Could not extract GrokAssembly.exe", ioe); + throw new InitializationException("Could not extract GrokAssembly.exe", ioe); } finally { if (fos != null) { try { @@ -219,6 +233,22 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { // Now, need to see if GrokAssembly actually runs from this location. final List args = buildArgumentList(); + //TODO this creaes an "unreported" error - if someone doesn't look + // at the command output this could easily be missed (especially in an + // Ant or Mmaven build. + // + // We need to create a non-fatal warning error type that will + // get added to the report. + //TOOD this idea needs to get replicated to the bundle audit analyzer. + if (args == null) { + setEnabled(false); + LOGGER.error("----------------------------------------------------"); + LOGGER.error(".NET Assembly Analyzer could not be initialized and at least one " + + "'exe' or 'dll' was scanned. The 'mono' executale could not be found on " + + "the path; either disable the Assembly Analyzer or configure the path mono."); + LOGGER.error("----------------------------------------------------"); + return; + } try { final ProcessBuilder pb = new ProcessBuilder(args); final Process p = pb.start(); @@ -232,19 +262,25 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer, please see the log for more details."); LOGGER.debug("GrokAssembly.exe is not working properly"); grokAssemblyExe = null; - this.setEnabled(false); - throw new AnalysisException("Could not execute .NET AssemblyAnalyzer"); + setEnabled(false); + throw new InitializationException("Could not execute .NET AssemblyAnalyzer"); } - } catch (AnalysisException e) { + } catch (InitializationException e) { + setEnabled(false); throw e; } catch (Throwable e) { LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer;\n" + "this can be ignored unless you are scanning .NET DLLs. Please see the log for more details."); LOGGER.debug("Could not execute GrokAssembly {}", e.getMessage()); - this.setEnabled(false); - throw new AnalysisException("An error occurred with the .NET AssemblyAnalyzer", e); + setEnabled(false); + throw new InitializationException("An error occurred with the .NET AssemblyAnalyzer", e); + } + try { + builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } catch (ParserConfigurationException ex) { + setEnabled(false); + throw new InitializationException("Error initializing the assembly analyzer", ex); } - builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } /** @@ -257,10 +293,12 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { super.close(); try { if (grokAssemblyExe != null && !grokAssemblyExe.delete()) { + LOGGER.debug("Unable to delete temporary GrokAssembly.exe; attempting delete on exit"); grokAssemblyExe.deleteOnExit(); } } catch (SecurityException se) { LOGGER.debug("Can't delete temporary GrokAssembly.exe"); + grokAssemblyExe.deleteOnExit(); } } @@ -296,7 +334,8 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Returns the key used in the properties file to reference the analyzer's enabled property. + * Returns the key used in the properties file to reference the analyzer's + * enabled property. * * @return the analyzer's enabled property setting key */ @@ -304,4 +343,29 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer { protected String getAnalyzerEnabledSettingKey() { return Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED; } + + /** + * Tests to see if a file is in the system path. Note - the current + * implementation only works on non-windows platforms. For purposes of the + * AssemblyAnalyzer this is okay as this is only needed on Mac/*nix. + * + * @param file the executable to look for + * @return true if the file exists; otherwise + * false + */ + private boolean isInPath(String file) { + final ProcessBuilder pb = new ProcessBuilder("which", file); + try { + final Process proc = pb.start(); + final int retCode = proc.waitFor(); + if (retCode == 0) { + return true; + } + } catch (IOException ex) { + LOGGER.debug("Path seach failed for " + file); + } catch (InterruptedException ex) { + LOGGER.debug("Path seach failed for " + file); + } + return false; + } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AutoconfAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AutoconfAnalyzer.java index 01f9080e5..2ed211adc 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AutoconfAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AutoconfAnalyzer.java @@ -35,13 +35,16 @@ import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.owasp.dependencycheck.exception.InitializationException; /** - * Used to analyze Autoconf input files named configure.ac or configure.in. Files simply named "configure" are also analyzed, - * assuming they are generated by Autoconf, and contain certain special package descriptor variables. + * Used to analyze Autoconf input files named configure.ac or configure.in. + * Files simply named "configure" are also analyzed, assuming they are generated + * by Autoconf, and contain certain special package descriptor variables. * * @author Dale Visser - * @see Autoconf - GNU Project - Free Software Foundation (FSF) + * @see Autoconf - GNU Project + * - Free Software Foundation (FSF) */ @Experimental public class AutoconfAnalyzer extends AbstractFileTypeAnalyzer { @@ -142,7 +145,8 @@ public class AutoconfAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Returns the key used in the properties file to reference the analyzer's enabled property. + * Returns the key used in the properties file to reference the analyzer's + * enabled property. * * @return the analyzer's enabled property setting key */ @@ -270,10 +274,11 @@ public class AutoconfAnalyzer extends AbstractFileTypeAnalyzer { /** * Initializes the file type analyzer. * - * @throws Exception thrown if there is an exception during initialization + * @throws InitializationException thrown if there is an exception during + * initialization */ @Override - protected void initializeFileTypeAnalyzer() throws Exception { + protected void initializeFileTypeAnalyzer() throws InitializationException { // No initialization needed. } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CMakeAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CMakeAnalyzer.java index 41c66d5a5..3220b30a8 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CMakeAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CMakeAnalyzer.java @@ -38,14 +38,18 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.owasp.dependencycheck.exception.InitializationException; /** *

- * Used to analyze CMake build files, and collect information that can be used to determine the associated CPE.

+ * Used to analyze CMake build files, and collect information that can be used + * to determine the associated CPE.

*

- * Note: This analyzer catches straightforward invocations of the project command, plus some other observed patterns of version - * inclusion in real CMake projects. Many projects make use of older versions of CMake and/or use custom "homebrew" ways to insert - * version information. Hopefully as the newer CMake call pattern grows in usage, this analyzer allow more CPEs to be + * Note: This analyzer catches straightforward invocations of the project + * command, plus some other observed patterns of version inclusion in real CMake + * projects. Many projects make use of older versions of CMake and/or use custom + * "homebrew" ways to insert version information. Hopefully as the newer CMake + * call pattern grows in usage, this analyzer allow more CPEs to be * identified.

* * @author Dale Visser @@ -135,10 +139,10 @@ public class CMakeAnalyzer extends AbstractFileTypeAnalyzer { /** * No-op initializer implementation. * - * @throws Exception never thrown + * @throws InitializationException never thrown */ @Override - protected void initializeFileTypeAnalyzer() throws Exception { + protected void initializeFileTypeAnalyzer() throws InitializationException { // Nothing to do here. } @@ -147,7 +151,8 @@ public class CMakeAnalyzer extends AbstractFileTypeAnalyzer { * * @param dependency the dependency being analyzed * @param engine the engine being used to perform the scan - * @throws AnalysisException thrown if there is an unrecoverable error analyzing the dependency + * @throws AnalysisException thrown if there is an unrecoverable error + * analyzing the dependency */ @Override protected void analyzeFileType(Dependency dependency, Engine engine) @@ -183,13 +188,17 @@ public class CMakeAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Extracts the version information from the contents. If more then one version is found additional dependencies are added to - * the dependency list. + * Extracts the version information from the contents. If more then one + * version is found additional dependencies are added to the dependency + * list. * * @param dependency the dependency being analyzed * @param engine the dependency-check engine * @param contents the version information */ + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( + value = "DM_DEFAULT_ENCODING", + justification = "Default encoding is only used if UTF-8 is not available") private void analyzeSetVersionCommand(Dependency dependency, Engine engine, String contents) { Dependency currentDep = dependency; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java index d2fcfb14a..cd1a194bd 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; import java.util.StringTokenizer; +import org.apache.commons.lang3.builder.CompareToBuilder; import org.apache.lucene.document.Document; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.queryparser.classic.ParseException; @@ -45,6 +46,7 @@ import org.owasp.dependencycheck.dependency.Evidence; import org.owasp.dependencycheck.dependency.EvidenceCollection; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.VulnerableSoftware; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.DependencyVersion; import org.owasp.dependencycheck.utils.DependencyVersionUtil; import org.slf4j.Logger; @@ -123,11 +125,20 @@ public class CPEAnalyzer implements Analyzer { /** * Creates the CPE Lucene Index. * - * @throws Exception is thrown if there is an issue opening the index. + * @throws InitializationException is thrown if there is an issue opening + * the index. */ @Override - public void initialize() throws Exception { - this.open(); + public void initialize() throws InitializationException { + try { + this.open(); + } catch (IOException ex) { + LOGGER.debug("Exception initializing the Lucene Index", ex); + throw new InitializationException("An exception occurred initializing the Lucene Index", ex); + } catch (DatabaseException ex) { + LOGGER.debug("Exception accessing the database", ex); + throw new InitializationException("An exception occurred accessing the database", ex); + } } /** @@ -540,7 +551,7 @@ public class CPEAnalyzer implements Analyzer { final List collected = new ArrayList(); //TODO the following algorithm incorrectly identifies things as a lower version - // if there lower confidence evidence when the current (highest) version number + // if there lower confidence evidence when the current (highest) version number // is newer then anything in the NVD. for (Confidence conf : Confidence.values()) { for (Evidence evidence : dependency.getVersionEvidence().iterator(conf)) { @@ -564,8 +575,9 @@ public class CPEAnalyzer implements Analyzer { final String url = String.format(NVD_SEARCH_URL, URLEncoder.encode(vs.getName(), "UTF-8")); final IdentifierMatch match = new IdentifierMatch("cpe", vs.getName(), url, IdentifierConfidence.EXACT_MATCH, conf); collected.add(match); - } else //TODO the following isn't quite right is it? need to think about this guessing game a bit more. - if (evVer.getVersionParts().size() <= dbVer.getVersionParts().size() + + //TODO the following isn't quite right is it? need to think about this guessing game a bit more. + } else if (evVer.getVersionParts().size() <= dbVer.getVersionParts().size() && evVer.matchesAtLeastThreeLevels(dbVer)) { if (bestGuessConf == null || bestGuessConf.compareTo(conf) > 0) { if (bestGuess.getVersionParts().size() < dbVer.getVersionParts().size()) { @@ -790,6 +802,12 @@ public class CPEAnalyzer implements Analyzer { */ @Override public int compareTo(IdentifierMatch o) { + return new CompareToBuilder() + .append(confidence, o.confidence) + .append(evidenceConfidence, o.evidenceConfidence) + .append(identifier, o.identifier) + .toComparison(); + /* int conf = this.confidence.compareTo(o.confidence); if (conf == 0) { conf = this.evidenceConfidence.compareTo(o.evidenceConfidence); @@ -798,6 +816,7 @@ public class CPEAnalyzer implements Analyzer { } } return conf; + */ } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CentralAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CentralAnalyzer.java index 08b9d4f63..38f7c9c2b 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CentralAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CentralAnalyzer.java @@ -33,8 +33,10 @@ import java.io.File; import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; import java.util.List; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.DownloadFailedException; import org.owasp.dependencycheck.utils.Downloader; import org.owasp.dependencycheck.utils.FileFilterBuilder; @@ -42,8 +44,8 @@ import org.owasp.dependencycheck.utils.InvalidSettingException; import org.owasp.dependencycheck.utils.Settings; /** - * Analyzer which will attempt to locate a dependency, and the GAV information, by querying Central for the dependency's SHA-1 - * digest. + * Analyzer which will attempt to locate a dependency, and the GAV information, + * by querying Central for the dependency's SHA-1 digest. * * @author colezlaw */ @@ -70,7 +72,8 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer { private static final String SUPPORTED_EXTENSIONS = "jar"; /** - * The analyzer should be disabled if there are errors, so this is a flag to determine if such an error has occurred. + * The analyzer should be disabled if there are errors, so this is a flag to + * determine if such an error has occurred. */ private boolean errorFlag = false; @@ -96,7 +99,8 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer { /** * Determines if this analyzer is enabled. * - * @return true if the analyzer is enabled; otherwise false + * @return true if the analyzer is enabled; otherwise + * false */ private boolean checkEnabled() { boolean retval = false; @@ -122,16 +126,21 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer { /** * Initializes the analyzer once before any analysis is performed. * - * @throws Exception if there's an error during initialization + * @throws InitializationException if there's an error during initialization */ @Override - public void initializeFileTypeAnalyzer() throws Exception { + public void initializeFileTypeAnalyzer() throws InitializationException { LOGGER.debug("Initializing Central analyzer"); LOGGER.debug("Central analyzer enabled: {}", isEnabled()); if (isEnabled()) { final String searchUrl = Settings.getString(Settings.KEYS.ANALYZER_CENTRAL_URL); LOGGER.debug("Central Analyzer URL: {}", searchUrl); - searcher = new CentralSearch(new URL(searchUrl)); + try { + searcher = new CentralSearch(new URL(searchUrl)); + } catch (MalformedURLException ex) { + setEnabled(false); + throw new InitializationException("The configured URL to Maven Central is malformed: " + searchUrl, ex); + } } } @@ -146,7 +155,8 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Returns the key used in the properties file to to reference the analyzer's enabled property. + * Returns the key used in the properties file to to reference the + * analyzer's enabled property. * * @return the analyzer's enabled property setting key. */ @@ -219,7 +229,8 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer { LOGGER.warn("Unable to download pom.xml for {} from Central; " + "this could result in undetected CPE/CVEs.", dependency.getFileName()); } finally { - if (pomFile != null && !FileUtils.deleteQuietly(pomFile)) { + if (pomFile != null && pomFile.exists() && !FileUtils.deleteQuietly(pomFile)) { + LOGGER.debug("Failed to delete temporary pom file {}", pomFile.toString()); pomFile.deleteOnExit(); } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CocoaPodsAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CocoaPodsAnalyzer.java new file mode 100644 index 000000000..1108d5e6a --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CocoaPodsAnalyzer.java @@ -0,0 +1,205 @@ +/* + * This file is part of dependency-check-core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2016 IBM Corporation. All Rights Reserved. + */ +package org.owasp.dependencycheck.analyzer; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.io.FileUtils; +import org.owasp.dependencycheck.Engine; +import org.owasp.dependencycheck.analyzer.exception.AnalysisException; +import org.owasp.dependencycheck.dependency.Confidence; +import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.dependency.EvidenceCollection; +import org.owasp.dependencycheck.utils.FileFilterBuilder; +import org.owasp.dependencycheck.utils.Settings; + +/** + * This analyzer is used to analyze SWIFT and Objective-C packages by collecting + * information from .podspec files. CocoaPods dependency manager see + * https://cocoapods.org/. + * + * @author Bianca Jiang (https://twitter.com/biancajiang) + */ +@Experimental +public class CocoaPodsAnalyzer extends AbstractFileTypeAnalyzer { + + /** + * The logger. + */ +// private static final Logger LOGGER = LoggerFactory.getLogger(CocoaPodsAnalyzer.class); + /** + * The name of the analyzer. + */ + private static final String ANALYZER_NAME = "CocoaPods Package Analyzer"; + + /** + * The phase that this analyzer is intended to run in. + */ + private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION; + + /** + * The file name to scan. + */ + public static final String PODSPEC = "podspec"; + /** + * Filter that detects files named "*.podspec". + */ + private static final FileFilter PODSPEC_FILTER = FileFilterBuilder.newInstance().addExtensions(PODSPEC).build(); + + /** + * The capture group #1 is the block variable. e.g. "Pod::Spec.new do + * |spec|" + */ + private static final Pattern PODSPEC_BLOCK_PATTERN = Pattern.compile("Pod::Spec\\.new\\s+?do\\s+?\\|(.+?)\\|"); + + /** + * Returns the FileFilter + * + * @return the FileFilter + */ + @Override + protected FileFilter getFileFilter() { + return PODSPEC_FILTER; + } + + @Override + protected void initializeFileTypeAnalyzer() { + // NO-OP + } + + /** + * Returns the name of the analyzer. + * + * @return the name of the analyzer. + */ + @Override + public String getName() { + return ANALYZER_NAME; + } + + /** + * Returns the phase that the analyzer is intended to run in. + * + * @return the phase that the analyzer is intended to run in. + */ + @Override + public AnalysisPhase getAnalysisPhase() { + return ANALYSIS_PHASE; + } + + /** + * Returns the key used in the properties file to reference the analyzer's + * enabled property. + * + * @return the analyzer's enabled property setting key + */ + @Override + protected String getAnalyzerEnabledSettingKey() { + return Settings.KEYS.ANALYZER_COCOAPODS_ENABLED; + } + + @Override + protected void analyzeFileType(Dependency dependency, Engine engine) + throws AnalysisException { + + String contents; + try { + contents = FileUtils.readFileToString(dependency.getActualFile(), Charset.defaultCharset()); + } catch (IOException e) { + throw new AnalysisException( + "Problem occurred while reading dependency file.", e); + } + final Matcher matcher = PODSPEC_BLOCK_PATTERN.matcher(contents); + if (matcher.find()) { + contents = contents.substring(matcher.end()); + final String blockVariable = matcher.group(1); + + final EvidenceCollection vendor = dependency.getVendorEvidence(); + final EvidenceCollection product = dependency.getProductEvidence(); + final EvidenceCollection version = dependency.getVersionEvidence(); + + final String name = addStringEvidence(product, contents, blockVariable, "name", "name", Confidence.HIGHEST); + if (!name.isEmpty()) { + vendor.addEvidence(PODSPEC, "name_project", name, Confidence.HIGHEST); + } + addStringEvidence(product, contents, blockVariable, "summary", "summary", Confidence.HIGHEST); + + addStringEvidence(vendor, contents, blockVariable, "author", "authors?", Confidence.HIGHEST); + addStringEvidence(vendor, contents, blockVariable, "homepage", "homepage", Confidence.HIGHEST); + addStringEvidence(vendor, contents, blockVariable, "license", "licen[cs]es?", Confidence.HIGHEST); + + addStringEvidence(version, contents, blockVariable, "version", "version", Confidence.HIGHEST); + } + + setPackagePath(dependency); + } + + /** + * Extracts evidence from the contents and adds it to the given evidence + * collection. + * + * @param evidences the evidence collection to update + * @param contents the text to extract evidence from + * @param blockVariable the block variable within the content to search for + * @param field the name of the field being searched for + * @param fieldPattern the field pattern within the contents to search for + * @param confidence the confidence level of the evidence if found + * @return the string that was added as evidence + */ + private String addStringEvidence(EvidenceCollection evidences, String contents, + String blockVariable, String field, String fieldPattern, Confidence confidence) { + String value = ""; + + //capture array value between [ ] + final Matcher arrayMatcher = Pattern.compile( + String.format("\\s*?%s\\.%s\\s*?=\\s*?\\{\\s*?(.*?)\\s*?\\}", blockVariable, fieldPattern), + Pattern.CASE_INSENSITIVE).matcher(contents); + if (arrayMatcher.find()) { + value = arrayMatcher.group(1); + } else { //capture single value between quotes + final Matcher matcher = Pattern.compile( + String.format("\\s*?%s\\.%s\\s*?=\\s*?(['\"])(.*?)\\1", blockVariable, fieldPattern), + Pattern.CASE_INSENSITIVE).matcher(contents); + if (matcher.find()) { + value = matcher.group(2); + } + } + if (value.length() > 0) { + evidences.addEvidence(PODSPEC, field, value, confidence); + } + return value; + } + + /** + * Sets the package path on the given dependency. + * + * @param dep the dependency to update + */ + private void setPackagePath(Dependency dep) { + final File file = new File(dep.getFilePath()); + final String parent = file.getParent(); + if (parent != null) { + dep.setPackagePath(parent); + } + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ComposerLockAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ComposerLockAnalyzer.java index df68ac8d3..ea4272121 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ComposerLockAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/ComposerLockAnalyzer.java @@ -35,6 +35,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.nio.charset.Charset; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import org.owasp.dependencycheck.exception.InitializationException; /** * Used to analyze a composer.lock file for a composer PHP app. @@ -77,15 +79,22 @@ public class ComposerLockAnalyzer extends AbstractFileTypeAnalyzer { /** * Initializes the analyzer. * - * @throws Exception thrown if an exception occurs getting an instance of SHA1 + * @throws InitializationException thrown if an exception occurs getting an + * instance of SHA1 */ @Override - protected void initializeFileTypeAnalyzer() throws Exception { - sha1 = MessageDigest.getInstance("SHA1"); + protected void initializeFileTypeAnalyzer() throws InitializationException { + try { + sha1 = MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException ex) { + setEnabled(false); + throw new InitializationException("Unable to create SHA1 MmessageDigest", ex); + } } /** - * The MessageDigest for calculating a new digest for the new dependencies added. + * The MessageDigest for calculating a new digest for the new dependencies + * added. */ private MessageDigest sha1 = null; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CpeSuppressionAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CpeSuppressionAnalyzer.java index a256c46f1..537fa731c 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CpeSuppressionAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CpeSuppressionAnalyzer.java @@ -20,7 +20,7 @@ package org.owasp.dependencycheck.analyzer; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.dependency.Dependency; -import org.owasp.dependencycheck.suppression.SuppressionRule; +import org.owasp.dependencycheck.xml.suppression.SuppressionRule; /** * The suppression analyzer processes an externally defined XML document that complies with the suppressions.xsd schema. diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java index 419eacb09..946194ab9 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java @@ -117,6 +117,7 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal final ListIterator subIterator = engine.getDependencies().listIterator(mainIterator.nextIndex()); while (subIterator.hasNext()) { final Dependency nextDependency = subIterator.next(); + Dependency main = null; if (hashesMatch(dependency, nextDependency) && !containedInWar(dependency.getFilePath()) && !containedInWar(nextDependency.getFilePath())) { if (firstPathIsShortest(dependency.getFilePath(), nextDependency.getFilePath())) { @@ -143,8 +144,14 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal mergeDependencies(nextDependency, dependency, dependenciesToRemove); break; //since we merged into the next dependency - skip forward to the next in mainIterator } - } else if (isSameRubyGem(dependency, nextDependency)) { - final Dependency main = getMainGemspecDependency(dependency, nextDependency); + } else if ((main = getMainGemspecDependency(dependency, nextDependency)) != null) { + if (main == dependency) { + mergeDependencies(dependency, nextDependency, dependenciesToRemove); + } else { + mergeDependencies(nextDependency, dependency, dependenciesToRemove); + break; //since we merged into the next dependency - skip forward to the next in mainIterator + } + } else if ((main = getMainSwiftDependency(dependency, nextDependency)) != null) { if (main == dependency) { mergeDependencies(dependency, nextDependency, dependenciesToRemove); } else { @@ -302,10 +309,13 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal String right = rFile.getParent(); if (left == null) { return right == null; + } else if (right == null) { + return false; } if (left.equalsIgnoreCase(right)) { return true; } + if (left.matches(".*[/\\\\]repository[/\\\\].*") && right.matches(".*[/\\\\]repository[/\\\\].*")) { left = getBaseRepoPath(left); right = getBaseRepoPath(right); @@ -376,6 +386,49 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal return null; } + /** + * Bundling same swift dependencies with the same packagePath but identified + * by different analyzers. + * + * @param dependency1 dependency to test + * @param dependency2 dependency to test + * @return true if the dependencies appear to be the same; + * otherwise false + */ + private boolean isSameSwiftPackage(Dependency dependency1, Dependency dependency2) { + if (dependency1 == null || dependency2 == null + || (!dependency1.getFileName().endsWith(".podspec") + && !dependency1.getFileName().equals("Package.swift")) + || (!dependency2.getFileName().endsWith(".podspec") + && !dependency2.getFileName().equals("Package.swift")) + || dependency1.getPackagePath() == null + || dependency2.getPackagePath() == null) { + return false; + } + if (dependency1.getPackagePath().equalsIgnoreCase(dependency2.getPackagePath())) { + return true; + } + return false; + } + + /** + * Determines which of the swift dependencies should be considered the + * primary. + * + * @param dependency1 the first swift dependency to compare + * @param dependency2 the second swift dependency to compare + * @return the primary swift dependency + */ + private Dependency getMainSwiftDependency(Dependency dependency1, Dependency dependency2) { + if (isSameSwiftPackage(dependency1, dependency2)) { + if (dependency1.getFileName().endsWith(".podspec")) { + return dependency1; + } + return dependency2; + } + return null; + } + /** * This is likely a very broken attempt at determining if the 'left' * dependency is the 'core' library in comparison to the 'right' library. diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzer.java index fcaaeb102..2aded7eb1 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FileNameAnalyzer.java @@ -70,11 +70,12 @@ public class FileNameAnalyzer extends AbstractAnalyzer implements Analyzer { /** * Python init files */ + //CSOFF: WhitespaceAfter private static final NameFileFilter IGNORED_FILES = new NameFileFilter(new String[]{ "__init__.py", "__init__.pyc", - "__init__.pyo", - }); + "__init__.pyo",}); + //CSON: WhitespaceAfter /** * Collects information about the file name. @@ -93,26 +94,27 @@ public class FileNameAnalyzer extends AbstractAnalyzer implements Analyzer { //add version evidence final DependencyVersion version = DependencyVersionUtil.parseVersion(fileName); + final String packageName = DependencyVersionUtil.parsePreVersion(fileName); if (version != null) { // If the version number is just a number like 2 or 23, reduce the confidence // a shade. This should hopefully correct for cases like log4j.jar or // struts2-core.jar if (version.getVersionParts() == null || version.getVersionParts().size() < 2) { - dependency.getVersionEvidence().addEvidence("file", "name", + dependency.getVersionEvidence().addEvidence("file", "version", version.toString(), Confidence.MEDIUM); } else { dependency.getVersionEvidence().addEvidence("file", "version", version.toString(), Confidence.HIGHEST); } dependency.getVersionEvidence().addEvidence("file", "name", - fileName, Confidence.MEDIUM); + packageName, Confidence.MEDIUM); } if (!IGNORED_FILES.accept(f)) { dependency.getProductEvidence().addEvidence("file", "name", - fileName, Confidence.HIGH); + packageName, Confidence.HIGH); dependency.getVendorEvidence().addEvidence("file", "name", - fileName, Confidence.HIGH); + packageName, Confidence.HIGH); } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java index 7279d11b8..beddaf39f 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java @@ -25,16 +25,13 @@ import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.Set; import java.util.regex.Pattern; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; -import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Evidence; -import org.owasp.dependencycheck.suppression.PropertyType; -import org.owasp.dependencycheck.suppression.SuppressionParseException; -import org.owasp.dependencycheck.suppression.SuppressionParser; +import org.owasp.dependencycheck.exception.InitializationException; +import org.owasp.dependencycheck.xml.suppression.PropertyType; import org.owasp.dependencycheck.utils.DownloadFailedException; import org.owasp.dependencycheck.utils.Downloader; import org.owasp.dependencycheck.utils.FileUtils; @@ -49,6 +46,8 @@ import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; /** + * This analyzer adds evidence to dependencies to enhance the accuracy of + * library identification. * * @author Jeremy Long */ @@ -87,12 +86,17 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer { /** * The initialize method does nothing for this Analyzer. * - * @throws Exception thrown if there is an exception + * @throws InitializationException thrown if there is an exception */ @Override - public void initialize() throws Exception { - super.initialize(); - loadHintRules(); + public void initialize() throws InitializationException { + try { + super.initialize(); + loadHintRules(); + } catch (HintParseException ex) { + LOGGER.debug("Unable to parse hint file", ex); + throw new InitializationException("Unable to parse the hint file", ex); + } } //
@@ -150,6 +154,9 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer { for (Evidence e : hint.getAddProduct()) { dependency.getProductEvidence().addEvidence(e); } + for (Evidence e : hint.getAddVersion()) { + dependency.getVersionEvidence().addEvidence(e); + } } } @@ -250,7 +257,7 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer { if (product.contains(zendframeworkProduct)) { dependency.getProductEvidence().addEvidence("hint analyzer", "vendor", "zend_framework", Confidence.HIGHEST); } - + //sun/oracle problem final Iterator itr = dependency.getVendorEvidence().iterator(); final List newEntries = new ArrayList(); @@ -274,7 +281,7 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer { /** * Loads the hint rules file. * - * @throws SuppressionParseException thrown if the XML cannot be parsed. + * @throws HintParseException thrown if the XML cannot be parsed. */ private void loadHintRules() throws HintParseException { final HintParser parser = new HintParser(); @@ -288,7 +295,7 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer { LOGGER.error("Unable to parse the base hint data file"); LOGGER.debug("Unable to parse the base hint data file", ex); } - final String filePath = Settings.getString(Settings.KEYS.SUPPRESSION_FILE); + final String filePath = Settings.getString(Settings.KEYS.HINTS_FILE); if (filePath == null) { return; } @@ -307,14 +314,21 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer { } else { file = new File(filePath); if (!file.exists()) { - final InputStream fromClasspath = this.getClass().getClassLoader().getResourceAsStream(filePath); - if (fromClasspath != null) { - deleteTempFile = true; - file = FileUtils.getTempFile("hint", "xml"); - try { - org.apache.commons.io.FileUtils.copyInputStreamToFile(fromClasspath, file); - } catch (IOException ex) { - throw new HintParseException("Unable to locate suppressions file in classpath", ex); + InputStream fromClasspath = null; + try { + fromClasspath = this.getClass().getClassLoader().getResourceAsStream(filePath); + if (fromClasspath != null) { + deleteTempFile = true; + file = FileUtils.getTempFile("hint", "xml"); + try { + org.apache.commons.io.FileUtils.copyInputStreamToFile(fromClasspath, file); + } catch (IOException ex) { + throw new HintParseException("Unable to locate suppressions file in classpath", ex); + } + } + } finally { + if (fromClasspath != null) { + fromClasspath.close(); } } } @@ -322,7 +336,7 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer { if (file != null) { try { - Hints newHints = parser.parseHints(file); + final Hints newHints = parser.parseHints(file); hints.getHintRules().addAll(newHints.getHintRules()); hints.getVendorDuplicatingHintRules().addAll(newHints.getVendorDuplicatingHintRules()); LOGGER.debug("{} hint rules were loaded.", hints.getHintRules().size()); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java index da6fb6078..67e371f62 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/JarAnalyzer.java @@ -49,6 +49,7 @@ import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.EvidenceCollection; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.FileFilterBuilder; import org.owasp.dependencycheck.xml.pom.License; import org.owasp.dependencycheck.xml.pom.PomUtils; @@ -324,8 +325,10 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } else { pom = PomUtils.readPom(externalPom); } - pom.processProperties(pomProperties); - foundSomething |= setPomEvidence(dependency, pom, classes); + if (pom != null) { + pom.processProperties(pomProperties); + foundSomething |= setPomEvidence(dependency, pom, classes); + } } } catch (AnalysisException ex) { LOGGER.warn("An error occurred while analyzing '{}'.", dependency.getActualFilePath()); @@ -408,6 +411,9 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { final File file = new File(tmpDir, "pom.xml"); try { final ZipEntry entry = jar.getEntry(path); + if (entry == null) { + throw new AnalysisException(String.format("Pom (%s)does not exist in %s", path, jar.getName())); + } input = jar.getInputStream(entry); fos = new FileOutputStream(file); IOUtils.copy(input, fos); @@ -486,7 +492,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } final String originalGroupID = groupid; - if (groupid.startsWith("org.") || groupid.startsWith("com.")) { + if (groupid != null && (groupid.startsWith("org.") || groupid.startsWith("com."))) { groupid = groupid.substring(4); } @@ -495,7 +501,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } final String originalArtifactID = artifactid; - if (artifactid.startsWith("org.") || artifactid.startsWith("com.")) { + if (artifactid != null && (artifactid.startsWith("org.") || artifactid.startsWith("com."))) { artifactid = artifactid.substring(4); } @@ -644,9 +650,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { * @return whether evidence was identified parsing the manifest * @throws IOException if there is an issue reading the JAR file */ - protected boolean parseManifest(Dependency dependency, - List classInformation) - throws IOException { + protected boolean parseManifest(Dependency dependency, List classInformation) throws IOException { boolean foundSomething = false; JarFile jar = null; try { @@ -665,7 +669,6 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { final EvidenceCollection vendorEvidence = dependency.getVendorEvidence(); final EvidenceCollection productEvidence = dependency.getProductEvidence(); final EvidenceCollection versionEvidence = dependency.getVersionEvidence(); - String source = "Manifest"; String specificationVersion = null; boolean hasImplementationVersion = false; @@ -687,7 +690,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { foundSomething = true; versionEvidence.addEvidence(source, key, value, Confidence.HIGH); } else if ("specification-version".equalsIgnoreCase(key)) { - specificationVersion = key; + specificationVersion = value; } else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) { foundSomething = true; vendorEvidence.addEvidence(source, key, value, Confidence.HIGH); @@ -706,17 +709,12 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { addMatchingValues(classInformation, value, productEvidence); // //the following caused false positives. // } else if (key.equalsIgnoreCase(BUNDLE_VENDOR)) { -// foundSomething = true; -// vendorEvidence.addEvidence(source, key, value, Confidence.HIGH); -// addMatchingValues(classInformation, value, vendorEvidence); } else if (key.equalsIgnoreCase(BUNDLE_VERSION)) { foundSomething = true; versionEvidence.addEvidence(source, key, value, Confidence.HIGH); } else if (key.equalsIgnoreCase(Attributes.Name.MAIN_CLASS.toString())) { continue; - //skipping main class as if this has important information to add - // it will be added during class name analysis... if other fields - // have the information from the class name then they will get added... + //skipping main class as if this has important information to add it will be added during class name analysis... } else { key = key.toLowerCase(); if (!IGNORE_KEYS.contains(key) @@ -782,7 +780,6 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { } } } - for (Map.Entry item : manifest.getEntries().entrySet()) { final String name = item.getKey(); source = "manifest: " + name; @@ -903,20 +900,27 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer { /** * Initializes the JarAnalyzer. * - * @throws Exception is thrown if there is an exception creating a temporary - * directory + * @throws InitializationException is thrown if there is an exception + * creating a temporary directory */ @Override - public void initializeFileTypeAnalyzer() throws Exception { - final File baseDir = Settings.getTempDirectory(); - tempFileLocation = File.createTempFile("check", "tmp", baseDir); - if (!tempFileLocation.delete()) { - final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath()); - throw new AnalysisException(msg); - } - if (!tempFileLocation.mkdirs()) { - final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath()); - throw new AnalysisException(msg); + public void initializeFileTypeAnalyzer() throws InitializationException { + try { + final File baseDir = Settings.getTempDirectory(); + tempFileLocation = File.createTempFile("check", "tmp", baseDir); + if (!tempFileLocation.delete()) { + final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath()); + setEnabled(false); + throw new InitializationException(msg); + } + if (!tempFileLocation.mkdirs()) { + final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath()); + setEnabled(false); + throw new InitializationException(msg); + } + } catch (IOException ex) { + setEnabled(false); + throw new InitializationException("Unable to create a temporary file", ex); } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java index c23322d76..4a89f7278 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java @@ -35,6 +35,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.DownloadFailedException; import org.owasp.dependencycheck.utils.Downloader; import org.owasp.dependencycheck.utils.FileFilterBuilder; @@ -42,15 +43,18 @@ import org.owasp.dependencycheck.utils.InvalidSettingException; import org.owasp.dependencycheck.utils.Settings; /** - * Analyzer which will attempt to locate a dependency on a Nexus service by SHA-1 digest of the dependency. + * Analyzer which will attempt to locate a dependency on a Nexus service by + * SHA-1 digest of the dependency. * * There are two settings which govern this behavior: * *
    - *
  • {@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_ENABLED} determines whether this analyzer is even - * enabled. This can be overridden by setting the system property.
  • - *
  • {@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_URL} the URL to a Nexus service to search by SHA-1. - * There is an expected %s in this where the SHA-1 will get entered.
  • + *
  • {@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_ENABLED} + * determines whether this analyzer is even enabled. This can be overridden by + * setting the system property.
  • + *
  • {@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_URL} + * the URL to a Nexus service to search by SHA-1. There is an expected + * %s in this where the SHA-1 will get entered.
  • *
* * @author colezlaw @@ -58,7 +62,8 @@ import org.owasp.dependencycheck.utils.Settings; public class NexusAnalyzer extends AbstractFileTypeAnalyzer { /** - * The default URL - this will be used by the CentralAnalyzer to determine whether to enable this. + * The default URL - this will be used by the CentralAnalyzer to determine + * whether to enable this. */ public static final String DEFAULT_URL = "https://repository.sonatype.org/service/local/"; @@ -95,7 +100,8 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer { /** * Determines if this analyzer is enabled * - * @return true if the analyzer is enabled; otherwise false + * @return true if the analyzer is enabled; otherwise + * false */ private boolean checkEnabled() { /* Enable this analyzer ONLY if the Nexus URL has been set to something @@ -131,10 +137,10 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer { /** * Initializes the analyzer once before any analysis is performed. * - * @throws Exception if there's an error during initialization + * @throws InitializationException if there's an error during initialization */ @Override - public void initializeFileTypeAnalyzer() throws Exception { + public void initializeFileTypeAnalyzer() throws InitializationException { LOGGER.debug("Initializing Nexus Analyzer"); LOGGER.debug("Nexus Analyzer enabled: {}", isEnabled()); if (isEnabled()) { @@ -143,14 +149,12 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer { try { searcher = new NexusSearch(new URL(searchUrl)); if (!searcher.preflightRequest()) { - LOGGER.warn("There was an issue getting Nexus status. Disabling analyzer."); setEnabled(false); + throw new InitializationException("There was an issue getting Nexus status. Disabling analyzer."); } } catch (MalformedURLException mue) { - // I know that initialize can throw an exception, but we'll - // just disable the analyzer if the URL isn't valid - LOGGER.warn("Property {} not a valid URL. Nexus Analyzer disabled", searchUrl); setEnabled(false); + throw new InitializationException("Malformed URL to Nexus: " + searchUrl, mue); } } } @@ -166,7 +170,8 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Returns the key used in the properties file to reference the analyzer's enabled property. + * Returns the key used in the properties file to reference the analyzer's + * enabled property. * * @return the analyzer's enabled property setting key */ @@ -240,7 +245,8 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer { LOGGER.warn("Unable to download pom.xml for {} from Nexus repository; " + "this could result in undetected CPE/CVEs.", dependency.getFileName()); } finally { - if (pomFile != null && !FileUtils.deleteQuietly(pomFile)) { + if (pomFile != null && pomFile.exists() && !FileUtils.deleteQuietly(pomFile)) { + LOGGER.debug("Failed to delete temporary pom file {}", pomFile.toString()); pomFile.deleteOnExit(); } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzer.java index 52b9afe11..50d20b38e 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzer.java @@ -38,10 +38,11 @@ import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonString; import javax.json.JsonValue; +import org.owasp.dependencycheck.exception.InitializationException; /** - * Used to analyze Node Package Manager (npm) package.json files, and collect information that can be used to determine the - * associated CPE. + * Used to analyze Node Package Manager (npm) package.json files, and collect + * information that can be used to determine the associated CPE. * * @author Dale Visser */ @@ -84,7 +85,7 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer { } @Override - protected void initializeFileTypeAnalyzer() throws Exception { + protected void initializeFileTypeAnalyzer() throws InitializationException { // NO-OP } @@ -109,7 +110,8 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Returns the key used in the properties file to reference the analyzer's enabled property. + * Returns the key used in the properties file to reference the analyzer's + * enabled property. * * @return the analyzer's enabled property setting key */ @@ -155,7 +157,8 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Adds information to an evidence collection from the node json configuration. + * Adds information to an evidence collection from the node json + * configuration. * * @param json information from node.js * @param collection a set of evidence about a dependency diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NuspecAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NuspecAnalyzer.java index d3950c793..b5d5de5b2 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NuspecAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NuspecAnalyzer.java @@ -34,6 +34,7 @@ import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import org.owasp.dependencycheck.exception.InitializationException; /** * Analyzer which will parse a Nuspec file to gather module information. @@ -65,10 +66,10 @@ public class NuspecAnalyzer extends AbstractFileTypeAnalyzer { /** * Initializes the analyzer once before any analysis is performed. * - * @throws Exception if there's an error during initialization + * @throws InitializationException if there's an error during initialization */ @Override - public void initializeFileTypeAnalyzer() throws Exception { + public void initializeFileTypeAnalyzer() throws InitializationException { } /** @@ -82,7 +83,8 @@ public class NuspecAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Returns the key used in the properties file to reference the analyzer's enabled property. + * Returns the key used in the properties file to reference the analyzer's + * enabled property. * * @return the analyzer's enabled property setting key */ diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NvdCveAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NvdCveAnalyzer.java index 249dd4855..fa0061f6a 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NvdCveAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NvdCveAnalyzer.java @@ -27,6 +27,8 @@ import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.exception.InitializationException; +import org.slf4j.LoggerFactory; /** * NvdCveAnalyzer is a utility class that takes a project dependency and attempts to discern if there is an associated @@ -35,7 +37,10 @@ import org.owasp.dependencycheck.dependency.Vulnerability; * @author Jeremy Long */ public class NvdCveAnalyzer implements Analyzer { - + /** + * The Logger for use throughout the class + */ + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(NvdCveAnalyzer.class); /** * The maximum number of query results to return. */ @@ -79,7 +84,7 @@ public class NvdCveAnalyzer implements Analyzer { /** * Ensures that the CVE Database is closed. * - * @throws Throwable when a throwable is thrown. + * @throws Throwable an exception raised by this method */ @Override protected void finalize() throws Throwable { @@ -94,7 +99,7 @@ public class NvdCveAnalyzer implements Analyzer { * * @param dependency The Dependency to analyze * @param engine The analysis engine - * @throws AnalysisException is thrown if there is an issue analyzing the dependency + * @throws AnalysisException thrown if there is an issue analyzing the dependency */ @Override public void analyze(Dependency dependency, Engine engine) throws AnalysisException { @@ -145,10 +150,24 @@ public class NvdCveAnalyzer implements Analyzer { /** * Opens the database used to gather NVD CVE data. * - * @throws Exception is thrown if there is an issue opening the index. + * @throws InitializationException is thrown if there is an issue opening the index. */ @Override - public void initialize() throws Exception { - this.open(); + public void initialize() throws InitializationException { + try { + this.open(); + } catch (SQLException ex) { + LOGGER.debug("SQL Exception initializing NvdCveAnalyzer", ex); + throw new InitializationException(ex); + } catch (IOException ex) { + LOGGER.debug("IO Exception initializing NvdCveAnalyzer", ex); + throw new InitializationException(ex); + } catch (DatabaseException ex) { + LOGGER.debug("Database Exception initializing NvdCveAnalyzer", ex); + throw new InitializationException(ex); + } catch (ClassNotFoundException ex) { + LOGGER.debug("Exception initializing NvdCveAnalyzer", ex); + throw new InitializationException(ex); + } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzer.java index bcc7728d7..c886814b6 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzer.java @@ -31,6 +31,7 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.owasp.dependencycheck.exception.InitializationException; /** * Used to analyze OpenSSL source code present in the file system. @@ -145,10 +146,10 @@ public class OpenSSLAnalyzer extends AbstractFileTypeAnalyzer { /** * No-op initializer implementation. * - * @throws Exception never thrown + * @throws InitializationException never thrown */ @Override - protected void initializeFileTypeAnalyzer() throws Exception { + protected void initializeFileTypeAnalyzer() throws InitializationException { // Nothing to do here. } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzer.java index 5fdadf0e6..a82b0ebd9 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonDistributionAnalyzer.java @@ -23,9 +23,10 @@ import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; import org.apache.commons.io.filefilter.NameFileFilter; import org.apache.commons.io.filefilter.SuffixFileFilter; -import org.apache.commons.io.input.AutoCloseInputStream; import org.apache.commons.lang3.StringUtils; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; @@ -37,6 +38,7 @@ import org.slf4j.LoggerFactory; import javax.mail.MessagingException; import javax.mail.internet.InternetHeaders; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.ExtractionException; import org.owasp.dependencycheck.utils.ExtractionUtil; import org.owasp.dependencycheck.utils.FileFilterBuilder; @@ -45,8 +47,9 @@ import org.owasp.dependencycheck.utils.Settings; import org.owasp.dependencycheck.utils.UrlStringUtils; /** - * Used to analyze a Wheel or egg distribution files, or their contents in unzipped form, and collect information that can be used - * to determine the associated CPE. + * Used to analyze a Wheel or egg distribution files, or their contents in + * unzipped form, and collect information that can be used to determine the + * associated CPE. * * @author Dale Visser */ @@ -70,7 +73,8 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { .getLogger(PythonDistributionAnalyzer.class); /** - * The count of directories created during analysis. This is used for creating temporary directories. + * The count of directories created during analysis. This is used for + * creating temporary directories. */ private static int dirCount = 0; @@ -104,7 +108,8 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { private File tempFileLocation; /** - * Filter that detects *.dist-info files (but doesn't verify they are directories. + * Filter that detects *.dist-info files (but doesn't verify they are + * directories. */ private static final FilenameFilter DIST_INFO_FILTER = new SuffixFileFilter( ".dist-info"); @@ -164,7 +169,8 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Returns the key used in the properties file to reference the analyzer's enabled property. + * Returns the key used in the properties file to reference the analyzer's + * enabled property. * * @return the analyzer's enabled property setting key */ @@ -206,7 +212,8 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { * @param dependency the archive being scanned * @param folderFilter the filter to apply to the folder * @param metadataFilter the filter to apply to the meta data - * @throws AnalysisException thrown when there is a problem analyzing the dependency + * @throws AnalysisException thrown when there is a problem analyzing the + * dependency */ private void collectMetadataFromArchiveFormat(Dependency dependency, FilenameFilter folderFilter, FilenameFilter metadataFilter) @@ -221,32 +228,43 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { throw new AnalysisException(ex); } - collectWheelMetadata( - dependency, - getMatchingFile(getMatchingFile(temp, folderFilter), - metadataFilter)); + File matchingFile = getMatchingFile(temp, folderFilter); + if (matchingFile != null) { + matchingFile = getMatchingFile(matchingFile, metadataFilter); + if (matchingFile != null) { + collectWheelMetadata(dependency, matchingFile); + } + } } /** * Makes sure a usable temporary directory is available. * - * @throws Exception an AnalyzeException is thrown when the temp directory cannot be created + * @throws InitializationException an AnalyzeException is thrown when the + * temp directory cannot be created */ @Override - protected void initializeFileTypeAnalyzer() throws Exception { - final File baseDir = Settings.getTempDirectory(); - tempFileLocation = File.createTempFile("check", "tmp", baseDir); - if (!tempFileLocation.delete()) { - final String msg = String.format( - "Unable to delete temporary file '%s'.", - tempFileLocation.getAbsolutePath()); - throw new AnalysisException(msg); - } - if (!tempFileLocation.mkdirs()) { - final String msg = String.format( - "Unable to create directory '%s'.", - tempFileLocation.getAbsolutePath()); - throw new AnalysisException(msg); + protected void initializeFileTypeAnalyzer() throws InitializationException { + try { + final File baseDir = Settings.getTempDirectory(); + tempFileLocation = File.createTempFile("check", "tmp", baseDir); + if (!tempFileLocation.delete()) { + setEnabled(false); + final String msg = String.format( + "Unable to delete temporary file '%s'.", + tempFileLocation.getAbsolutePath()); + throw new InitializationException(msg); + } + if (!tempFileLocation.mkdirs()) { + setEnabled(false); + final String msg = String.format( + "Unable to create directory '%s'.", + tempFileLocation.getAbsolutePath()); + throw new InitializationException(msg); + } + } catch (IOException ex) { + setEnabled(false); + throw new InitializationException("Unable to create a temporary file", ex); } } @@ -312,7 +330,8 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { } /** - * Returns a list of files that match the given filter, this does not recursively scan the directory. + * Returns a list of files that match the given filter, this does not + * recursively scan the directory. * * @param folder the folder to filter * @param filter the filter to apply to the files in the directory @@ -338,20 +357,30 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer { if (null == manifest) { LOGGER.debug("Manifest file not found."); } else { + InputStream in = null; try { - result.load(new AutoCloseInputStream(new BufferedInputStream( - new FileInputStream(manifest)))); + in = new BufferedInputStream(new FileInputStream(manifest)); + result.load(in); } catch (MessagingException e) { LOGGER.warn(e.getMessage(), e); } catch (FileNotFoundException e) { LOGGER.warn(e.getMessage(), e); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ex) { + LOGGER.debug("failed to close input stream", ex); + } + } } } return result; } /** - * Retrieves the next temporary destination directory for extracting an archive. + * Retrieves the next temporary destination directory for extracting an + * archive. * * @return a directory * @throws AnalysisException thrown if unable to create temporary directory diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonPackageAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonPackageAnalyzer.java index e0b8aa9ab..df532e051 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonPackageAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/PythonPackageAnalyzer.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.owasp.dependencycheck.exception.InitializationException; /** * Used to analyze a Python package, and collect information that can be used to @@ -144,10 +145,10 @@ public class PythonPackageAnalyzer extends AbstractFileTypeAnalyzer { /** * No-op initializer implementation. * - * @throws Exception never thrown + * @throws InitializationException never thrown */ @Override - protected void initializeFileTypeAnalyzer() throws Exception { + protected void initializeFileTypeAnalyzer() throws InitializationException { // Nothing to do here. } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzer.java index 087184bdd..bca567fa5 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzer.java @@ -22,24 +22,27 @@ import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.nio.charset.Charset; + import org.apache.commons.io.FileUtils; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.data.nvdcve.CveDB; +import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Reference; import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.FileFilterBuilder; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.owasp.dependencycheck.data.nvdcve.DatabaseException; /** * Used to analyze Ruby Bundler Gemspec.lock files utilizing the 3rd party @@ -50,6 +53,9 @@ import org.owasp.dependencycheck.data.nvdcve.DatabaseException; @Experimental public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer { + /** + * The logger. + */ private static final Logger LOGGER = LoggerFactory.getLogger(RubyBundleAuditAnalyzer.class); /** @@ -126,10 +132,10 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer { * Initialize the analyzer. In this case, extract GrokAssembly.exe to a * temporary location. * - * @throws Exception if anything goes wrong + * @throws InitializationException if anything goes wrong */ @Override - public void initializeFileTypeAnalyzer() throws Exception { + public void initializeFileTypeAnalyzer() throws InitializationException { try { cvedb = new CveDB(); cvedb.open(); @@ -137,25 +143,36 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer { LOGGER.warn("Exception opening the database"); LOGGER.debug("error", ex); setEnabled(false); - throw ex; + throw new InitializationException("Error connecting to the database", ex); } // Now, need to see if bundle-audit actually runs from this location. Process process = null; try { process = launchBundleAudit(Settings.getTempDirectory()); } catch (AnalysisException ae) { - LOGGER.warn("Exception from bundle-audit process: {}. Disabling {}", ae.getCause(), ANALYZER_NAME); + setEnabled(false); cvedb.close(); cvedb = null; - throw ae; + final String msg = String.format("Exception from bundle-audit process: %s. Disabling %s", ae.getCause(), ANALYZER_NAME); + throw new InitializationException(msg, ae); + } catch (IOException ex) { + setEnabled(false); + throw new InitializationException("Unable to create temporary file, the Ruby Bundle Audit Analyzer will be disabled", ex); } - final int exitValue = process.waitFor(); - if (0 == exitValue) { - LOGGER.warn("Unexpected exit code from bundle-audit process. Disabling {}: {}", ANALYZER_NAME, exitValue); + final int exitValue; + try { + exitValue = process.waitFor(); + } catch (InterruptedException ex) { setEnabled(false); - throw new AnalysisException("Unexpected exit code from bundle-audit process."); + final String msg = String.format("Bundle-audit process was interupted. Disabling %s", ANALYZER_NAME); + throw new InitializationException(msg); + } + if (0 == exitValue) { + setEnabled(false); + final String msg = String.format("Unexpected exit code from bundle-audit process. Disabling %s: %s", ANALYZER_NAME, exitValue); + throw new InitializationException(msg); } else { BufferedReader reader = null; try { @@ -163,18 +180,28 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer { if (!reader.ready()) { LOGGER.warn("Bundle-audit error stream unexpectedly not ready. Disabling " + ANALYZER_NAME); setEnabled(false); - throw new AnalysisException("Bundle-audit error stream unexpectedly not ready."); + throw new InitializationException("Bundle-audit error stream unexpectedly not ready."); } else { final String line = reader.readLine(); if (line == null || !line.contains("Errno::ENOENT")) { LOGGER.warn("Unexpected bundle-audit output. Disabling {}: {}", ANALYZER_NAME, line); setEnabled(false); - throw new AnalysisException("Unexpected bundle-audit output."); + throw new InitializationException("Unexpected bundle-audit output."); } } + } catch (UnsupportedEncodingException ex) { + setEnabled(false); + throw new InitializationException("Unexpected bundle-audit encoding.", ex); + } catch (IOException ex) { + setEnabled(false); + throw new InitializationException("Unable to read bundle-audit output.", ex); } finally { if (null != reader) { - reader.close(); + try { + reader.close(); + } catch (IOException ex) { + LOGGER.debug("Error closing reader", ex); + } } } } @@ -253,11 +280,16 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer { } final File parentFile = dependency.getActualFile().getParentFile(); final Process process = launchBundleAudit(parentFile); + final int exitValue; try { - process.waitFor(); + exitValue = process.waitFor(); } catch (InterruptedException ie) { throw new AnalysisException("bundle-audit process interrupted", ie); } + if (exitValue != 0) { + final String msg = String.format("Unexpected exit code from bundle-audit process; exit code: %s", exitValue); + throw new AnalysisException(msg); + } BufferedReader rdr = null; BufferedReader errReader = null; try { @@ -456,7 +488,9 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer { */ private Dependency createDependencyForGem(Engine engine, String parentName, String fileName, String filePath, String gem) throws IOException { final File gemFile = new File(Settings.getTempDirectory(), gem + "_Gemfile.lock"); - gemFile.createNewFile(); + if (!gemFile.createNewFile()) { + throw new IOException("Unable to create temporary gem file"); + } final String displayFileName = String.format("%s%c%s:%s", parentName, File.separatorChar, fileName, gem); FileUtils.write(gemFile, displayFileName, Charset.defaultCharset()); // unique contents to avoid dependency bundling diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundlerAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundlerAnalyzer.java index ebe77e7b8..df394760f 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundlerAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyBundlerAnalyzer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright (c) 2016 Bianca Jiang. All Rights Reserved. + * Copyright (c) 2016 IBM Corporation. All Rights Reserved. */ package org.owasp.dependencycheck.analyzer; @@ -43,7 +43,7 @@ import org.owasp.dependencycheck.dependency.Dependency; * {@link RubyGemspecAnalyzer}, so it will enabled/disabled with * {@link RubyGemspecAnalyzer}. * - * @author Bianca Jiang (biancajiang@gmail.com) + * @author Bianca Jiang (https://twitter.com/biancajiang) */ @Experimental public class RubyBundlerAnalyzer extends RubyGemspecAnalyzer { diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyGemspecAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyGemspecAnalyzer.java index 56faf90a6..020f15434 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyGemspecAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/RubyGemspecAnalyzer.java @@ -32,6 +32,7 @@ import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.EvidenceCollection; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.FileFilterBuilder; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; @@ -88,7 +89,7 @@ public class RubyGemspecAnalyzer extends AbstractFileTypeAnalyzer { } @Override - protected void initializeFileTypeAnalyzer() throws Exception { + protected void initializeFileTypeAnalyzer() throws InitializationException { // NO-OP } @@ -216,6 +217,9 @@ public class RubyGemspecAnalyzer extends AbstractFileTypeAnalyzer { return name.contains(VERSION_FILE_NAME); } }); + if (matchingFiles == null) { + return; + } for (File f : matchingFiles) { try { final List lines = FileUtils.readLines(f, Charset.defaultCharset()); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/SwiftPackageManagerAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/SwiftPackageManagerAnalyzer.java new file mode 100644 index 000000000..d0a6bb0b9 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/SwiftPackageManagerAnalyzer.java @@ -0,0 +1,192 @@ +/* + * This file is part of dependency-check-core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2016 IBM Corporation. All Rights Reserved. + */ +package org.owasp.dependencycheck.analyzer; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.io.FileUtils; +import org.owasp.dependencycheck.Engine; +import org.owasp.dependencycheck.analyzer.exception.AnalysisException; +import org.owasp.dependencycheck.dependency.Confidence; +import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.dependency.EvidenceCollection; +import org.owasp.dependencycheck.utils.FileFilterBuilder; +import org.owasp.dependencycheck.utils.Settings; + +/** + * This analyzer is used to analyze the SWIFT Package Manager + * (https://swift.org/package-manager/). It collects information about a package + * from Package.swift files. + * + * @author Bianca Jiang (https://twitter.com/biancajiang) + */ +@Experimental +public class SwiftPackageManagerAnalyzer extends AbstractFileTypeAnalyzer { + + /** + * The name of the analyzer. + */ + private static final String ANALYZER_NAME = "SWIFT Package Manager Analyzer"; + + /** + * The phase that this analyzer is intended to run in. + */ + private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION; + + /** + * The file name to scan. + */ + public static final String SPM_FILE_NAME = "Package.swift"; + + /** + * Filter that detects files named "package.json". + */ + private static final FileFilter SPM_FILE_FILTER = FileFilterBuilder.newInstance().addFilenames(SPM_FILE_NAME).build(); + + /** + * The capture group #1 is the block variable. e.g. "import + * PackageDescription let package = Package( name: "Gloss" )" + */ + private static final Pattern SPM_BLOCK_PATTERN = Pattern.compile("let[^=]+=\\s*Package\\s*\\(\\s*([^)]*)\\s*\\)", Pattern.DOTALL); + + /** + * Returns the FileFilter + * + * @return the FileFilter + */ + @Override + protected FileFilter getFileFilter() { + return SPM_FILE_FILTER; + } + + @Override + protected void initializeFileTypeAnalyzer() { + // NO-OP + } + + /** + * Returns the name of the analyzer. + * + * @return the name of the analyzer. + */ + @Override + public String getName() { + return ANALYZER_NAME; + } + + /** + * Returns the phase that the analyzer is intended to run in. + * + * @return the phase that the analyzer is intended to run in. + */ + @Override + public AnalysisPhase getAnalysisPhase() { + return ANALYSIS_PHASE; + } + + /** + * Returns the key used in the properties file to reference the analyzer's + * enabled property. + * + * @return the analyzer's enabled property setting key + */ + @Override + protected String getAnalyzerEnabledSettingKey() { + return Settings.KEYS.ANALYZER_SWIFT_PACKAGE_MANAGER_ENABLED; + } + + @Override + protected void analyzeFileType(Dependency dependency, Engine engine) + throws AnalysisException { + + String contents; + try { + contents = FileUtils.readFileToString(dependency.getActualFile(), Charset.defaultCharset()); + } catch (IOException e) { + throw new AnalysisException( + "Problem occurred while reading dependency file.", e); + } + final Matcher matcher = SPM_BLOCK_PATTERN.matcher(contents); + if (matcher.find()) { + final String packageDescription = matcher.group(1); + if (packageDescription.isEmpty()) { + return; + } + + final EvidenceCollection product = dependency.getProductEvidence(); + final EvidenceCollection vendor = dependency.getVendorEvidence(); + + //SPM is currently under development for SWIFT 3. Its current metadata includes package name and dependencies. + //Future interesting metadata: version, license, homepage, author, summary, etc. + final String name = addStringEvidence(product, packageDescription, "name", "name", Confidence.HIGHEST); + if (name != null && !name.isEmpty()) { + vendor.addEvidence(SPM_FILE_NAME, "name_project", name, Confidence.HIGHEST); + } + } + setPackagePath(dependency); + } + + /** + * Extracts evidence from the package description and adds it to the given + * evidence collection. + * + * @param evidences the evidence collection to update + * @param packageDescription the text to extract evidence from + * @param field the name of the field being searched for + * @param fieldPattern the field pattern within the contents to search for + * @param confidence the confidence level of the evidence if found + * @return the string that was added as evidence + */ + private String addStringEvidence(EvidenceCollection evidences, + String packageDescription, String field, String fieldPattern, Confidence confidence) { + String value = ""; + + final Matcher matcher = Pattern.compile( + String.format("%s *:\\s*\"([^\"]*)", fieldPattern), Pattern.DOTALL).matcher(packageDescription); + if (matcher.find()) { + value = matcher.group(1); + } + + if (value != null) { + value = value.trim(); + if (value.length() > 0) { + evidences.addEvidence(SPM_FILE_NAME, field, value, confidence); + } + } + + return value; + } + + /** + * Sets the package path on the given dependency. + * + * @param dep the dependency to update + */ + private void setPackagePath(Dependency dep) { + final File file = new File(dep.getFilePath()); + final String parent = file.getParent(); + if (parent != null) { + dep.setPackagePath(parent); + } + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/VulnerabilitySuppressionAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/VulnerabilitySuppressionAnalyzer.java index 11dc9c825..4ceac47ce 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/VulnerabilitySuppressionAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/VulnerabilitySuppressionAnalyzer.java @@ -20,7 +20,7 @@ package org.owasp.dependencycheck.analyzer; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.dependency.Dependency; -import org.owasp.dependencycheck.suppression.SuppressionRule; +import org.owasp.dependencycheck.xml.suppression.SuppressionRule; /** * The suppression analyzer processes an externally defined XML document that complies with the suppressions.xsd schema. diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/central/CentralSearch.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/central/CentralSearch.java index d4ba768c1..5a9641dd4 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/central/CentralSearch.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/central/CentralSearch.java @@ -61,8 +61,8 @@ public class CentralSearch { /** * Creates a NexusSearch for the given repository URL. * - * @param rootURL the URL of the repository on which searches should execute. Only parameters are added to this (so it should - * end in /select) + * @param rootURL the URL of the repository on which searches should + * execute. Only parameters are added to this (so it should end in /select) */ public CentralSearch(URL rootURL) { this.rootURL = rootURL; @@ -76,18 +76,20 @@ public class CentralSearch { } /** - * Searches the configured Central URL for the given sha1 hash. If the artifact is found, a MavenArtifact is - * populated with the GAV. + * Searches the configured Central URL for the given sha1 hash. If the + * artifact is found, a MavenArtifact is populated with the + * GAV. * * @param sha1 the SHA-1 hash string for which to search * @return the populated Maven GAV. - * @throws IOException if it's unable to connect to the specified repository or if the specified artifact is not found. + * @throws IOException if it's unable to connect to the specified repository + * or if the specified artifact is not found. */ public List searchSha1(String sha1) throws IOException { if (null == sha1 || !sha1.matches("^[0-9A-Fa-f]{40}$")) { throw new IllegalArgumentException("Invalid SHA1 format"); } - + List result = null; final URL url = new URL(rootURL + String.format("?q=1:\"%s\"&wt=xml", sha1)); LOGGER.debug("Searching Central url {}", url); @@ -116,7 +118,7 @@ public class CentralSearch { if ("0".equals(numFound)) { missing = true; } else { - final List result = new ArrayList(); + result = new ArrayList(); final NodeList docs = (NodeList) xpath.evaluate("/response/result/doc", doc, XPathConstants.NODESET); for (int i = 0; i < docs.getLength(); i++) { final String g = xpath.evaluate("./str[@name='g']", docs.item(i)); @@ -144,16 +146,12 @@ public class CentralSearch { useHTTPS = true; } } - LOGGER.trace("Version: {}", v); result.add(new MavenArtifact(g, a, v, jarAvailable, pomAvailable, useHTTPS)); } - - return result; } } catch (Throwable e) { - // Anything else is jacked up XML stuff that we really can't recover - // from well + // Anything else is jacked up XML stuff that we really can't recover from well throw new IOException(e.getMessage(), e); } @@ -162,10 +160,9 @@ public class CentralSearch { } } else { LOGGER.debug("Could not connect to Central received response code: {} {}", - conn.getResponseCode(), conn.getResponseMessage()); + conn.getResponseCode(), conn.getResponseMessage()); throw new IOException("Could not connect to Central"); } - - return null; + return result; } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/CpeMemoryIndex.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/CpeMemoryIndex.java index 666a2ffbe..d6677cd71 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/CpeMemoryIndex.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/cpe/CpeMemoryIndex.java @@ -38,7 +38,6 @@ import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.RAMDirectory; -import org.owasp.dependencycheck.data.lucene.FieldAnalyzer; import org.owasp.dependencycheck.data.lucene.LuceneUtils; import org.owasp.dependencycheck.data.lucene.SearchFieldAnalyzer; import org.owasp.dependencycheck.data.nvdcve.CveDB; @@ -48,8 +47,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * An in memory lucene index that contains the vendor/product combinations from the CPE (application) identifiers within the NVD - * CVE data. + * An in memory lucene index that contains the vendor/product combinations from + * the CPE (application) identifiers within the NVD CVE data. * * @author Jeremy Long */ @@ -144,19 +143,6 @@ public final class CpeMemoryIndex { return openState; } - /** - * Creates the indexing analyzer for the CPE Index. - * - * @return the CPE Analyzer. - * @deprecated the search field analyzer must be used to include the token concatenating filter. - */ - @Deprecated - private Analyzer createIndexingAnalyzer() { - final Map fieldAnalyzers = new HashMap(); - fieldAnalyzers.put(Fields.DOCUMENT_KEY, new KeywordAnalyzer()); - return new PerFieldAnalyzerWrapper(new FieldAnalyzer(LuceneUtils.CURRENT_VERSION), fieldAnalyzers); - } - /** * Creates an Analyzer for searching the CPE Index. * @@ -223,10 +209,13 @@ public final class CpeMemoryIndex { final Set> data = cve.getVendorProductList(); for (Pair pair : data) { - v.setStringValue(pair.getLeft()); - p.setStringValue(pair.getRight()); - indexWriter.addDocument(doc); - resetFieldAnalyzer(); + //todo figure out why there are null products + if (pair.getLeft() != null && pair.getRight() != null) { + v.setStringValue(pair.getLeft()); + p.setStringValue(pair.getRight()); + indexWriter.addDocument(doc); + resetFieldAnalyzer(); + } } } catch (DatabaseException ex) { LOGGER.debug("", ex); @@ -275,7 +264,8 @@ public final class CpeMemoryIndex { * @param maxQueryResults the maximum number of documents to return * @return the TopDocs found by the search * @throws ParseException thrown when the searchString is invalid - * @throws IOException is thrown if there is an issue with the underlying Index + * @throws IOException is thrown if there is an issue with the underlying + * Index */ public TopDocs search(String searchString, int maxQueryResults) throws ParseException, IOException { if (searchString == null || searchString.trim().isEmpty()) { diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/FieldAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/FieldAnalyzer.java index 534259f07..0736c9fb0 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/FieldAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/lucene/FieldAnalyzer.java @@ -34,7 +34,7 @@ import org.apache.lucene.util.Version; * index the CPE fields vendor and product.

* * @author Jeremy Long - * @Deprecated the field analyzer should not be used, instead use the + * @deprecated the field analyzer should not be used, instead use the * SearchFieldAnalyzer so that the token analyzing filter is used. */ @Deprecated diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/ConnectionFactory.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/ConnectionFactory.java index 8961b1533..a5918ca47 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/ConnectionFactory.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/ConnectionFactory.java @@ -20,7 +20,7 @@ package org.owasp.dependencycheck.data.nvdcve; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.sql.CallableStatement; +import java.sql.PreparedStatement; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; @@ -36,8 +36,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Loads the configured database driver and returns the database connection. If the embedded H2 database is used obtaining a - * connection will ensure the database file exists and that the appropriate table structure has been created. + * Loads the configured database driver and returns the database connection. If + * the embedded H2 database is used obtaining a connection will ensure the + * database file exists and that the appropriate table structure has been + * created. * * @author Jeremy Long */ @@ -87,12 +89,13 @@ public final class ConnectionFactory { } /** - * Initializes the connection factory. Ensuring that the appropriate drivers are loaded and that a connection can be made - * successfully. + * Initializes the connection factory. Ensuring that the appropriate drivers + * are loaded and that a connection can be made successfully. * - * @throws DatabaseException thrown if we are unable to connect to the database + * @throws DatabaseException thrown if we are unable to connect to the + * database */ - public static synchronized void initialize() throws DatabaseException { + public static void initialize() throws DatabaseException { //this only needs to be called once. if (connectionString != null) { return; @@ -188,11 +191,12 @@ public final class ConnectionFactory { } /** - * Cleans up resources and unloads any registered database drivers. This needs to be called to ensure the driver is - * unregistered prior to the finalize method being called as during shutdown the class loader used to load the driver may be - * unloaded prior to the driver being de-registered. + * Cleans up resources and unloads any registered database drivers. This + * needs to be called to ensure the driver is unregistered prior to the + * finalize method being called as during shutdown the class loader used to + * load the driver may be unloaded prior to the driver being de-registered. */ - public static synchronized void cleanup() { + public static void cleanup() { if (driver != null) { try { DriverManager.deregisterDriver(driver); @@ -210,10 +214,12 @@ public final class ConnectionFactory { } /** - * Constructs a new database connection object per the database configuration. + * Constructs a new database connection object per the database + * configuration. * * @return a database connection object - * @throws DatabaseException thrown if there is an exception loading the database connection + * @throws DatabaseException thrown if there is an exception loading the + * database connection */ public static Connection getConnection() throws DatabaseException { initialize(); @@ -228,10 +234,12 @@ public final class ConnectionFactory { } /** - * Determines if the H2 database file exists. If it does not exist then the data structure will need to be created. + * Determines if the H2 database file exists. If it does not exist then the + * data structure will need to be created. * * @return true if the H2 database file does not exist; otherwise false - * @throws IOException thrown if the data directory does not exist and cannot be created + * @throws IOException thrown if the data directory does not exist and + * cannot be created */ private static boolean h2DataFileExists() throws IOException { final File dir = Settings.getDataDirectory(); @@ -241,7 +249,8 @@ public final class ConnectionFactory { } /** - * Creates the database structure (tables and indexes) to store the CVE data. + * Creates the database structure (tables and indexes) to store the CVE + * data. * * @param conn the database connection * @throws DatabaseException thrown if there is a Database Exception @@ -271,14 +280,17 @@ public final class ConnectionFactory { } /** - * Updates the database schema by loading the upgrade script for the version specified. The intended use is that if the - * current schema version is 2.9 then we would call updateSchema(conn, "2.9"). This would load the upgrade_2.9.sql file and - * execute it against the database. The upgrade script must update the 'version' in the properties table. + * Updates the database schema by loading the upgrade script for the version + * specified. The intended use is that if the current schema version is 2.9 + * then we would call updateSchema(conn, "2.9"). This would load the + * upgrade_2.9.sql file and execute it against the database. The upgrade + * script must update the 'version' in the properties table. * * @param conn the database connection object * @param appExpectedVersion the schema version that the application expects * @param currentDbVersion the current schema version of the database - * @throws DatabaseException thrown if there is an exception upgrading the database schema + * @throws DatabaseException thrown if there is an exception upgrading the + * database schema */ private static void updateSchema(Connection conn, DependencyVersion appExpectedVersion, DependencyVersion currentDbVersion) throws DatabaseException { @@ -340,26 +352,35 @@ public final class ConnectionFactory { } /** - * Counter to ensure that calls to ensureSchemaVersion does not end up in an endless loop. + * Counter to ensure that calls to ensureSchemaVersion does not end up in an + * endless loop. */ private static int callDepth = 0; /** - * Uses the provided connection to check the specified schema version within the database. + * Uses the provided connection to check the specified schema version within + * the database. * * @param conn the database connection object - * @throws DatabaseException thrown if the schema version is not compatible with this version of dependency-check + * @throws DatabaseException thrown if the schema version is not compatible + * with this version of dependency-check */ private static void ensureSchemaVersion(Connection conn) throws DatabaseException { ResultSet rs = null; - CallableStatement cs = null; + PreparedStatement ps = null; try { //TODO convert this to use DatabaseProperties - cs = conn.prepareCall("SELECT value FROM properties WHERE id = 'version'"); - rs = cs.executeQuery(); + ps = conn.prepareStatement("SELECT value FROM properties WHERE id = 'version'"); + rs = ps.executeQuery(); if (rs.next()) { final DependencyVersion appDbVersion = DependencyVersionUtil.parseVersion(DB_SCHEMA_VERSION); + if (appDbVersion == null) { + throw new DatabaseException("Invalid application database schema"); + } final DependencyVersion db = DependencyVersionUtil.parseVersion(rs.getString(1)); + if (db == null) { + throw new DatabaseException("Invalid database schema"); + } if (appDbVersion.compareTo(db) > 0) { LOGGER.debug("Current Schema: {}", DB_SCHEMA_VERSION); LOGGER.debug("DB Schema: {}", rs.getString(1)); @@ -376,7 +397,7 @@ public final class ConnectionFactory { throw new DatabaseException("Unable to check the database schema version"); } finally { DBUtils.closeResultSet(rs); - DBUtils.closeStatement(cs); + DBUtils.closeStatement(ps); } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java index 82e738b0c..1588a717f 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nvdcve/CveDB.java @@ -19,7 +19,6 @@ package org.owasp.dependencycheck.data.nvdcve; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -69,17 +68,16 @@ public class CveDB { private ResourceBundle statementBundle = null; /** - * Creates a new CveDB object and opens the database - * connection. Note, the connection must be closed by the caller by calling - * the close method. ======= Does the underlying connection support batch - * operations? + * Creates a new CveDB object and opens the database connection. Note, the + * connection must be closed by the caller by calling the close method. + * ======= Does the underlying connection support batch operations? */ private boolean batchSupported; /** * Creates a new CveDB object and opens the database connection. Note, the * connection must be closed by the caller by calling the close method. - * + * * @throws DatabaseException thrown if there is an exception opening the * database. */ @@ -89,10 +87,12 @@ public class CveDB { open(); try { final String databaseProductName = conn.getMetaData().getDatabaseProductName(); - batchSupported = conn.getMetaData().supportsBatchUpdates(); LOGGER.debug("Database dialect: {}", databaseProductName); final Locale dbDialect = new Locale(databaseProductName); statementBundle = ResourceBundle.getBundle("data/dbStatements", dbDialect); + if ("mysql".equalsIgnoreCase(databaseProductName)) { + batchSupported = false; + } } catch (SQLException se) { LOGGER.warn("Problem loading database specific dialect!", se); statementBundle = ResourceBundle.getBundle("data/dbStatements"); @@ -660,7 +660,7 @@ public class CveDB { + "If the problem persist try deleting the files in '{}' and running {} again. If the problem continues, please " + "create a log file (see documentation at http://jeremylong.github.io/DependencyCheck/) and open a ticket at " + "https://github.com/jeremylong/DependencyCheck/issues and include the log file.\n\n", - dd, dd, Settings.getString(Settings.KEYS.APPLICATION_VAME)); + dd, dd, Settings.getString(Settings.KEYS.APPLICATION_NAME)); LOGGER.debug("", ex); } finally { DBUtils.closeResultSet(rs); @@ -813,14 +813,14 @@ public class CveDB { * Deletes unused dictionary entries from the database. */ public void deleteUnusedCpe() { - CallableStatement cs = null; + PreparedStatement ps = null; try { - cs = getConnection().prepareCall(statementBundle.getString("DELETE_UNUSED_DICT_CPE")); - cs.executeUpdate(); + ps = getConnection().prepareStatement(statementBundle.getString("DELETE_UNUSED_DICT_CPE")); + ps.executeUpdate(); } catch (SQLException ex) { LOGGER.error("Unable to delete CPE dictionary entries", ex); } finally { - DBUtils.closeStatement(cs); + DBUtils.closeStatement(ps); } } @@ -837,7 +837,7 @@ public class CveDB { public void addCpe(String cpe, String vendor, String product) { PreparedStatement ps = null; try { - ps = getConnection().prepareCall(statementBundle.getString("ADD_DICT_CPE")); + ps = getConnection().prepareStatement(statementBundle.getString("ADD_DICT_CPE")); ps.setString(1, cpe); ps.setString(2, vendor); ps.setString(3, product); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/CpeUpdater.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/CpeUpdater.java index 0c158a92d..778ae124a 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/CpeUpdater.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/CpeUpdater.java @@ -158,6 +158,7 @@ public class CpeUpdater extends BaseUpdater implements CachedWebDataSource { final String originalPath = file.getPath(); final File gzip = new File(originalPath + ".gz"); if (gzip.isFile() && !gzip.delete()) { + LOGGER.debug("Failed to delete intial temporary file {}", gzip.toString()); gzip.deleteOnExit(); } if (!file.renameTo(gzip)) { @@ -192,8 +193,9 @@ public class CpeUpdater extends BaseUpdater implements CachedWebDataSource { LOGGER.trace("ignore", ex); } } - if (gzip.isFile()) { - FileUtils.deleteQuietly(gzip); + if (gzip.isFile() && !FileUtils.deleteQuietly(gzip)) { + LOGGER.debug("Failed to delete temporary file {}", gzip.toString()); + gzip.deleteOnExit(); } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/EngineVersionCheck.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/EngineVersionCheck.java index d642b1aac..690b3aa75 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/EngineVersionCheck.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/EngineVersionCheck.java @@ -36,6 +36,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** + * Checks the gh-pages dependency-check site to determine the current released + * version number. If the released version number is greater then the running + * version number a warning is printed recommending that an upgrade be + * performed. * * @author Jeremy Long */ @@ -59,12 +63,14 @@ public class EngineVersionCheck implements CachedWebDataSource { private CveDB cveDB = null; /** - * The version retrieved from the database properties or web to check against. + * The version retrieved from the database properties or web to check + * against. */ private String updateToVersion; /** - * Getter for updateToVersion - only used for testing. Represents the version retrieved from the database. + * Getter for updateToVersion - only used for testing. Represents the + * version retrieved from the database. * * @return the version to test */ @@ -73,7 +79,8 @@ public class EngineVersionCheck implements CachedWebDataSource { } /** - * Setter for updateToVersion - only used for testing. Represents the version retrieved from the database. + * Setter for updateToVersion - only used for testing. Represents the + * version retrieved from the database. * * @param version the version to test */ @@ -81,9 +88,16 @@ public class EngineVersionCheck implements CachedWebDataSource { updateToVersion = version; } + /** + * Downloads the current released version number and compares it to the + * running engine's version number. If the released version number is newer + * a warning is printed recommending an upgrade. + * + * @throws UpdateException thrown if the local database properties could not + * be updated + */ @Override public void update() throws UpdateException { - try { if (Settings.getBoolean(Settings.KEYS.AUTO_UPDATE)) { openDatabase(); @@ -109,20 +123,21 @@ public class EngineVersionCheck implements CachedWebDataSource { LOGGER.debug("Unable to determine if autoupdate is enabled", ex); } finally { closeDatabase(); - } } /** - * Determines if a new version of the dependency-check engine has been released. + * Determines if a new version of the dependency-check engine has been + * released. * * @param lastChecked the epoch time of the last version check * @param now the current epoch time * @param properties the database properties object * @param currentVersion the current version of dependency-check - * @return true if a newer version of the database has been released; otherwise false - * @throws UpdateException thrown if there is an error connecting to the github documentation site or accessing the local - * database. + * @return true if a newer version of the database has been + * released; otherwise false + * @throws UpdateException thrown if there is an error connecting to the + * github documentation site or accessing the local database. */ protected boolean shouldUpdate(final long lastChecked, final long now, final DatabaseProperties properties, String currentVersion) throws UpdateException { @@ -185,7 +200,8 @@ public class EngineVersionCheck implements CachedWebDataSource { } /** - * Retrieves the current released version number from the github documentation site. + * Retrieves the current released version number from the github + * documentation site. * * @return the current released version number */ @@ -204,11 +220,11 @@ public class EngineVersionCheck implements CachedWebDataSource { return releaseVersion.trim(); } } catch (MalformedURLException ex) { - LOGGER.debug("unable to retrieve current release version of dependency-check", ex); + LOGGER.debug("Unable to retrieve current release version of dependency-check - malformed url?"); } catch (URLConnectionFailureException ex) { - LOGGER.debug("unable to retrieve current release version of dependency-check", ex); + LOGGER.debug("Unable to retrieve current release version of dependency-check - connection failed"); } catch (IOException ex) { - LOGGER.debug("unable to retrieve current release version of dependency-check", ex); + LOGGER.debug("Unable to retrieve current release version of dependency-check - i/o exception"); } finally { if (conn != null) { conn.disconnect(); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java index 8aa06c797..b28209af0 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/NvdCveUpdater.java @@ -50,7 +50,7 @@ import org.slf4j.LoggerFactory; public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource { /** - * The logger + * The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(NvdCveUpdater.class); /** @@ -59,9 +59,8 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource { public static final int MAX_THREAD_POOL_SIZE = Settings.getInt(Settings.KEYS.MAX_DOWNLOAD_THREAD_POOL_SIZE, 3); /** - *

* Downloads the latest NVD CVE XML file from the web and imports it into - * the current CVE Database.

+ * the current CVE Database. * * @throws UpdateException is thrown if there is an error updating the * database @@ -78,15 +77,13 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource { } if (autoUpdate && checkUpdate()) { final UpdateableNvdCve updateable = getUpdatesNeeded(); - getProperties().save(DatabaseProperties.LAST_CHECKED, Long.toString(System.currentTimeMillis())); if (updateable.isUpdateNeeded()) { performUpdate(updateable); } + getProperties().save(DatabaseProperties.LAST_CHECKED, Long.toString(System.currentTimeMillis())); } } catch (MalformedURLException ex) { - LOGGER.warn( - "NVD CVE properties files contain an invalid URL, unable to update the data to use the most current data."); - LOGGER.debug("", ex); + throw new UpdateException("NVD CVE properties files contain an invalid URL, unable to update the data to use the most current data.", ex); } catch (DownloadFailedException ex) { LOGGER.warn( "Unable to download the NVD CVE data; the results may not include the most recent CPE/CVEs from the NVD."); @@ -94,7 +91,7 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource { LOGGER.info( "If you are behind a proxy you may need to configure dependency-check to use the proxy."); } - LOGGER.debug("", ex); + throw new UpdateException("Unable to download the NVD CVE data.", ex); } finally { closeDataStores(); } @@ -107,9 +104,9 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource { * checking again. A database property stores the timestamp of the last * check. * - * @return true to proceed with the check, or false to skip. + * @return true to proceed with the check, or false to skip * @throws UpdateException thrown when there is an issue checking for - * updates. + * updates */ private boolean checkUpdate() throws UpdateException { boolean proceed = true; @@ -159,94 +156,86 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource { * @throws UpdateException is thrown if there is an error updating the * database */ - public void performUpdate(UpdateableNvdCve updateable) throws UpdateException { + private void performUpdate(UpdateableNvdCve updateable) throws UpdateException { int maxUpdates = 0; - try { - for (NvdCveInfo cve : updateable) { - if (cve.getNeedsUpdate()) { - maxUpdates += 1; + for (NvdCveInfo cve : updateable) { + if (cve.getNeedsUpdate()) { + maxUpdates += 1; + } + } + if (maxUpdates <= 0) { + return; + } + if (maxUpdates > 3) { + LOGGER.info("NVD CVE requires several updates; this could take a couple of minutes."); + } + + final int poolSize = (MAX_THREAD_POOL_SIZE < maxUpdates) ? MAX_THREAD_POOL_SIZE : maxUpdates; + + final ExecutorService downloadExecutors = Executors.newFixedThreadPool(poolSize); + final ExecutorService processExecutor = Executors.newSingleThreadExecutor(); + final Set>> downloadFutures = new HashSet>>(maxUpdates); + for (NvdCveInfo cve : updateable) { + if (cve.getNeedsUpdate()) { + final DownloadTask call = new DownloadTask(cve, processExecutor, getCveDB(), Settings.getInstance()); + downloadFutures.add(downloadExecutors.submit(call)); + } + } + downloadExecutors.shutdown(); + + //next, move the future future processTasks to just future processTasks + final Set> processFutures = new HashSet>(maxUpdates); + for (Future> future : downloadFutures) { + Future task = null; + try { + task = future.get(); + } catch (InterruptedException ex) { + downloadExecutors.shutdownNow(); + processExecutor.shutdownNow(); + + LOGGER.debug("Thread was interrupted during download", ex); + throw new UpdateException("The download was interrupted", ex); + } catch (ExecutionException ex) { + downloadExecutors.shutdownNow(); + processExecutor.shutdownNow(); + + LOGGER.debug("Thread was interrupted during download execution", ex); + throw new UpdateException("The execution of the download was interrupted", ex); + } + if (task == null) { + downloadExecutors.shutdownNow(); + processExecutor.shutdownNow(); + LOGGER.debug("Thread was interrupted during download"); + throw new UpdateException("The download was interrupted; unable to complete the update"); + } else { + processFutures.add(task); + } + } + + for (Future future : processFutures) { + try { + final ProcessTask task = future.get(); + if (task.getException() != null) { + throw task.getException(); } + } catch (InterruptedException ex) { + processExecutor.shutdownNow(); + LOGGER.debug("Thread was interrupted during processing", ex); + throw new UpdateException(ex); + } catch (ExecutionException ex) { + processExecutor.shutdownNow(); + LOGGER.debug("Execution Exception during process", ex); + throw new UpdateException(ex); + } finally { + processExecutor.shutdown(); } - if (maxUpdates <= 0) { - return; - } - if (maxUpdates > 3) { - LOGGER.info( - "NVD CVE requires several updates; this could take a couple of minutes."); - } - if (maxUpdates > 0) { - openDataStores(); - } + } - final int poolSize = (MAX_THREAD_POOL_SIZE < maxUpdates) ? MAX_THREAD_POOL_SIZE : maxUpdates; - - final ExecutorService downloadExecutors = Executors.newFixedThreadPool(poolSize); - final ExecutorService processExecutor = Executors.newSingleThreadExecutor(); - final Set>> downloadFutures = new HashSet>>(maxUpdates); - for (NvdCveInfo cve : updateable) { - if (cve.getNeedsUpdate()) { - final DownloadTask call = new DownloadTask(cve, processExecutor, getCveDB(), Settings.getInstance()); - downloadFutures.add(downloadExecutors.submit(call)); - } - } - downloadExecutors.shutdown(); - - //next, move the future future processTasks to just future processTasks - final Set> processFutures = new HashSet>(maxUpdates); - for (Future> future : downloadFutures) { - Future task = null; - try { - task = future.get(); - } catch (InterruptedException ex) { - downloadExecutors.shutdownNow(); - processExecutor.shutdownNow(); - - LOGGER.debug("Thread was interrupted during download", ex); - throw new UpdateException("The download was interrupted", ex); - } catch (ExecutionException ex) { - downloadExecutors.shutdownNow(); - processExecutor.shutdownNow(); - - LOGGER.debug("Thread was interrupted during download execution", ex); - throw new UpdateException("The execution of the download was interrupted", ex); - } - if (task == null) { - downloadExecutors.shutdownNow(); - processExecutor.shutdownNow(); - LOGGER.debug("Thread was interrupted during download"); - throw new UpdateException("The download was interrupted; unable to complete the update"); - } else { - processFutures.add(task); - } - } - - for (Future future : processFutures) { - try { - final ProcessTask task = future.get(); - if (task.getException() != null) { - throw task.getException(); - } - } catch (InterruptedException ex) { - processExecutor.shutdownNow(); - LOGGER.debug("Thread was interrupted during processing", ex); - throw new UpdateException(ex); - } catch (ExecutionException ex) { - processExecutor.shutdownNow(); - LOGGER.debug("Execution Exception during process", ex); - throw new UpdateException(ex); - } finally { - processExecutor.shutdown(); - } - } - - if (maxUpdates >= 1) { //ensure the modified file date gets written (we may not have actually updated it) - getProperties().save(updateable.get(MODIFIED)); - LOGGER.info("Begin database maintenance."); - getCveDB().cleanupDatabase(); - LOGGER.info("End database maintenance."); - } - } finally { - closeDataStores(); + if (maxUpdates >= 1) { //ensure the modified file date gets written (we may not have actually updated it) + getProperties().save(updateable.get(MODIFIED)); + LOGGER.info("Begin database maintenance."); + getCveDB().cleanupDatabase(); + LOGGER.info("End database maintenance."); } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/cpe/CPEHandler.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/cpe/CPEHandler.java index 24293b969..4c778e7b2 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/cpe/CPEHandler.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/cpe/CPEHandler.java @@ -43,9 +43,10 @@ public class CPEHandler extends DefaultHandler { /** * The Starts with expression to filter CVE entries by CPE. */ - private static final String CPE_STARTS_WITH = Settings.getString(Settings.KEYS.CVE_CPE_STARTS_WITH_FILTER,"cpe:/a:"); + private static final String CPE_STARTS_WITH = Settings.getString(Settings.KEYS.CVE_CPE_STARTS_WITH_FILTER, "cpe:/a:"); /** - * The text content of the node being processed. This can be used during the end element event. + * The text content of the node being processed. This can be used during the + * end element event. */ private StringBuilder nodeText = null; /** @@ -77,7 +78,8 @@ public class CPEHandler extends DefaultHandler { * @param localName the local name * @param qName the qualified name * @param attributes the attributes - * @throws SAXException thrown if there is an exception processing the element + * @throws SAXException thrown if there is an exception processing the + * element */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { @@ -128,7 +130,8 @@ public class CPEHandler extends DefaultHandler { * @param ch the char array * @param start the start position of the data read * @param length the length of the data read - * @throws SAXException thrown if there is an exception processing the characters + * @throws SAXException thrown if there is an exception processing the + * characters */ @Override public void characters(char[] ch, int start, int length) throws SAXException { @@ -138,12 +141,14 @@ public class CPEHandler extends DefaultHandler { } /** - * Handles the end element event. Stores the CPE data in the Cve Database if the cpe item node is ending. + * Handles the end element event. Stores the CPE data in the Cve Database if + * the cpe item node is ending. * * @param uri the element's uri * @param localName the local name * @param qName the qualified name - * @throws SAXException thrown if there is an exception processing the element + * @throws SAXException thrown if there is an exception processing the + * element */ @Override public void endElement(String uri, String localName, String qName) throws SAXException { @@ -182,7 +187,8 @@ public class CPEHandler extends DefaultHandler { // /** - * A simple class to maintain information about the current element while parsing the CPE XML. + * A simple class to maintain information about the current element while + * parsing the CPE XML. */ protected static final class Element { diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/exception/UpdateException.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/exception/UpdateException.java index 69d6448f4..a09c17040 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/exception/UpdateException.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/exception/UpdateException.java @@ -17,14 +17,12 @@ */ package org.owasp.dependencycheck.data.update.exception; -import java.io.IOException; - /** * An exception used when an error occurs reading a setting. * * @author Jeremy Long */ -public class UpdateException extends IOException { +public class UpdateException extends Exception { /** * The serial version uid. diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/DownloadTask.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/DownloadTask.java index 020c2263c..f6d29e811 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/DownloadTask.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/DownloadTask.java @@ -55,8 +55,9 @@ public class DownloadTask implements Callable> { * @param nvdCveInfo the NVD CVE info * @param processor the processor service to submit the downloaded files to * @param cveDB the CVE DB to use to store the vulnerability data - * @param settings a reference to the global settings object; this is necessary so that when the thread is started the - * dependencies have a correct reference to the global settings. + * @param settings a reference to the global settings object; this is + * necessary so that when the thread is started the dependencies have a + * correct reference to the global settings. * @throws UpdateException thrown if temporary files could not be created */ public DownloadTask(NvdCveInfo nvdCveInfo, ExecutorService processor, CveDB cveDB, Settings settings) throws UpdateException { @@ -205,25 +206,13 @@ public class DownloadTask implements Callable> { * Attempts to delete the files that were downloaded. */ public void cleanup() { - boolean deleted = false; - try { - if (first != null && first.exists()) { - deleted = first.delete(); - } - } finally { - if (first != null && (first.exists() || !deleted)) { - first.deleteOnExit(); - } + if (first != null && first.exists() && first.delete()) { + LOGGER.debug("Failed to delete first temporary file {}", second.toString()); + first.deleteOnExit(); } - try { - deleted = false; - if (second != null && second.exists()) { - deleted = second.delete(); - } - } finally { - if (second != null && (second.exists() || !deleted)) { - second.deleteOnExit(); - } + if (second != null && second.exists() && !second.delete()) { + LOGGER.debug("Failed to delete second temporary file {}", second.toString()); + second.deleteOnExit(); } } @@ -268,7 +257,8 @@ public class DownloadTask implements Callable> { } /** - * Extracts the file contained in a gzip archive. The extracted file is placed in the exact same path as the file specified. + * Extracts the file contained in a gzip archive. The extracted file is + * placed in the exact same path as the file specified. * * @param file the archive file * @throws FileNotFoundException thrown if the file does not exist @@ -278,6 +268,7 @@ public class DownloadTask implements Callable> { final String originalPath = file.getPath(); final File gzip = new File(originalPath + ".gz"); if (gzip.isFile() && !gzip.delete()) { + LOGGER.debug("Failed to delete initial temporary file when extracting 'gz' {}", gzip.toString()); gzip.deleteOnExit(); } if (!file.renameTo(gzip)) { @@ -312,8 +303,9 @@ public class DownloadTask implements Callable> { LOGGER.trace("ignore", ex); } } - if (gzip.isFile()) { - FileUtils.deleteQuietly(gzip); + if (gzip.isFile() && !FileUtils.deleteQuietly(gzip)) { + LOGGER.debug("Failed to delete temporary file when extracting 'gz' {}", gzip.toString()); + gzip.deleteOnExit(); } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/UpdateableNvdCve.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/UpdateableNvdCve.java index 0d5762708..6df4e5fa6 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/UpdateableNvdCve.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/update/nvd/UpdateableNvdCve.java @@ -36,6 +36,9 @@ import org.slf4j.LoggerFactory; */ public class UpdateableNvdCve implements Iterable, Iterator { + /** + * A reference to the logger. + */ private static final Logger LOGGER = LoggerFactory.getLogger(UpdateableNvdCve.class); /** * A collection of sources of data. diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Reference.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Reference.java index 5be391a27..7e6baebd6 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Reference.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Reference.java @@ -18,6 +18,7 @@ package org.owasp.dependencycheck.dependency; import java.io.Serializable; +import org.apache.commons.lang3.builder.CompareToBuilder; /** * An external reference for a vulnerability. This contains a name, URL, and a @@ -141,18 +142,10 @@ public class Reference implements Serializable, Comparable { */ @Override public int compareTo(Reference o) { - if (source.equals(o.source)) { - if (name.equals(o.name)) { - if (url.equals(o.url)) { - return 0; //they are equal - } else { - return url.compareTo(o.url); - } - } else { - return name.compareTo(o.name); - } - } else { - return source.compareTo(o.source); - } + return new CompareToBuilder() + .append(source, o.source) + .append(name, o.name) + .append(url, o.url) + .toComparison(); } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java index 8d8f01cd1..5666c7d10 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Vulnerability.java @@ -21,6 +21,7 @@ import java.io.Serializable; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import org.apache.commons.lang3.builder.CompareToBuilder; /** * Contains the information about a vulnerability. @@ -161,7 +162,8 @@ public class Vulnerability implements Serializable, Comparable { * Adds an entry for vulnerable software. * * @param cpe string representation of a cpe - * @param previousVersion the previous version (previousVersion - cpe would be considered vulnerable) + * @param previousVersion the previous version (previousVersion - cpe would + * be considered vulnerable) * @return if the add succeeded */ public boolean addVulnerableSoftware(String cpe, String previousVersion) { @@ -390,28 +392,32 @@ public class Vulnerability implements Serializable, Comparable { sb.append(this.name); sb.append("\nReferences:\n"); for (Reference reference : this.references) { - sb.append("=> "); - sb.append(reference); - sb.append("\n"); + sb.append("=> "); + sb.append(reference); + sb.append("\n"); } sb.append("\nSoftware:\n"); for (VulnerableSoftware software : this.vulnerableSoftware) { - sb.append("=> "); - sb.append(software); - sb.append("\n"); + sb.append("=> "); + sb.append(software); + sb.append("\n"); } return sb.toString(); } + /** * Compares two vulnerabilities. * * @param v a vulnerability to be compared - * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than - * the specified vulnerability + * @return a negative integer, zero, or a positive integer as this object is + * less than, equal to, or greater than the specified vulnerability */ @Override public int compareTo(Vulnerability v) { - return v.getName().compareTo(this.getName()); + return new CompareToBuilder() + .append(this.name, v.name) + .toComparison(); + //return v.getName().compareTo(this.getName()); } /** @@ -427,8 +433,8 @@ public class Vulnerability implements Serializable, Comparable { * Sets the CPE that caused this vulnerability to be flagged. * * @param cpeId a CPE identifier - * @param previous a flag indicating whether or not all previous versions were affected (any non-null value is - * considered true) + * @param previous a flag indicating whether or not all previous versions + * were affected (any non-null value is considered true) */ public void setMatchedCPE(String cpeId, String previous) { matchedCPE = cpeId; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ExceptionCollection.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ExceptionCollection.java new file mode 100644 index 000000000..1932dc79a --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ExceptionCollection.java @@ -0,0 +1,227 @@ +/* + * This file is part of dependency-check-core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2016 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.exception; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * A collection of several exceptions. + * + * @author Jeremy Lomg + */ +public class ExceptionCollection extends Exception { + + /** + * Instantiates a new exception collection. + * + * @param exceptions a list of exceptions + */ + public ExceptionCollection(List exceptions) { + super(); + this.exceptions = exceptions; + } + + /** + * Instantiates a new exception collection. + * + * @param msg the exception message + * @param exceptions a list of exceptions + */ + public ExceptionCollection(String msg, List exceptions) { + super(msg); + this.exceptions = exceptions; + } + + /** + * Instantiates a new exception collection. + * + * @param exceptions a list of exceptions + * @param fatal indicates if the exception that occurred is fatal - meaning + * that no analysis was performed. + */ + public ExceptionCollection(List exceptions, boolean fatal) { + super(); + this.exceptions = exceptions; + this.fatal = fatal; + } + + /** + * Instantiates a new exception collection. + * + * @param msg the exception message + * @param exceptions a list of exceptions + * @param fatal indicates if the exception that occurred is fatal - meaning + * that no analysis was performed. + */ + public ExceptionCollection(String msg, List exceptions, boolean fatal) { + super(msg); + this.exceptions = exceptions; + this.fatal = fatal; + } + + /** + * Instantiates a new exception collection. + * + * @param exceptions a list of exceptions + * @param fatal indicates if the exception that occurred is fatal - meaning + * that no analysis was performed. + */ + public ExceptionCollection(Throwable exceptions, boolean fatal) { + super(); + this.exceptions = new ArrayList(); + this.exceptions.add(exceptions); + this.fatal = fatal; + } + + /** + * Instantiates a new exception collection. + * + * @param msg the exception message + * @param exception a list of exceptions + */ + public ExceptionCollection(String msg, Throwable exception) { + super(msg); + this.exceptions = new ArrayList(); + this.exceptions.add(exception); + this.fatal = false; + } + + /** + * Instantiates a new exception collection. + */ + public ExceptionCollection() { + super(); + this.exceptions = new ArrayList(); + } + /** + * The serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * A collection of exceptions. + */ + private List exceptions; + + /** + * Get the value of exceptions. + * + * @return the value of exceptions + */ + public List getExceptions() { + return exceptions; + } + + /** + * Adds an exception to the collection. + * + * @param ex the exception to add + */ + public void addException(Throwable ex) { + this.exceptions.add(ex); + } + + /** + * Adds an exception to the collection. + * + * @param ex the exception to add + * @param fatal flag indicating if this is a fatal error + */ + public void addException(Throwable ex, boolean fatal) { + addException(ex); + this.fatal = fatal; + } + + /** + * Flag indicating if a fatal exception occurred that would prevent the + * attempt at completing the analysis even if exceptions occurred. + */ + private boolean fatal = false; + + /** + * Get the value of fatal. + * + * @return the value of fatal + */ + public boolean isFatal() { + return fatal; + } + + /** + * Set the value of fatal. + * + * @param fatal new value of fatal + */ + public void setFatal(boolean fatal) { + this.fatal = fatal; + } + + /** + * Prints the stack trace. + * + * @param s the writer to print to + */ + @Override + public void printStackTrace(PrintWriter s) { + s.println("Multiple Exceptions Occured"); + super.printStackTrace(s); + for (Throwable t : this.exceptions) { + s.println("Next Exception:"); + t.printStackTrace(s); + } + } + + /** + * Prints the stack trace. + * + * @param s the stream to write the stack trace to + */ + @Override + public void printStackTrace(PrintStream s) { + s.println("Multiple Exceptions Occurred"); + super.printStackTrace(s); + for (Throwable t : this.exceptions) { + s.println("Next Exception:"); + t.printStackTrace(s); + } + } + + /** + * Returns the error message, including the message from all contained + * exceptions. + * + * @return the error message + */ + @Override + public String getMessage() { + final StringBuilder sb = new StringBuilder(); + final String msg = super.getMessage(); + if (msg == null || msg.isEmpty()) { + sb.append("One or more exceptions occured during analysis:"); + } else { + sb.append(msg); + } + for (Throwable t : this.exceptions) { + sb.append("\n\t").append(t.getMessage()); + } + return sb.toString(); + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/InitializationException.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/InitializationException.java new file mode 100644 index 000000000..f818ebff6 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/InitializationException.java @@ -0,0 +1,66 @@ +/* + * This file is part of dependency-check-core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2016 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.exception; + +/** + * An exception used when initializing analyzers. + * + * @author Jeremy Long + */ +public class InitializationException extends Exception { + + /** + * The serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Creates a new InitializationException. + */ + public InitializationException() { + super(); + } + + /** + * Creates a new InitializationException. + * + * @param msg a message for the exception. + */ + public InitializationException(String msg) { + super(msg); + } + + /** + * Creates a new InitializationException. + * + * @param ex the cause of the exception. + */ + public InitializationException(Throwable ex) { + super(ex); + } + + /** + * Creates a new InitializationException. + * + * @param msg a message for the exception. + * @param ex the cause of the exception. + */ + public InitializationException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ReportException.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ReportException.java new file mode 100644 index 000000000..7199b3520 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/ReportException.java @@ -0,0 +1,66 @@ +/* + * This file is part of dependency-check-core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2016 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.exception; + +/** + * An exception used when generating reports. + * + * @author Jeremy Long + */ +public class ReportException extends Exception { + + /** + * The serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Creates a new ReportException. + */ + public ReportException() { + super(); + } + + /** + * Creates a new ReportException. + * + * @param msg a message for the exception. + */ + public ReportException(String msg) { + super(msg); + } + + /** + * Creates a new ReportException. + * + * @param ex the cause of the exception. + */ + public ReportException(Throwable ex) { + super(ex); + } + + /** + * Creates a new ReportException. + * + * @param msg a message for the exception. + * @param ex the cause of the exception. + */ + public ReportException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java index b47f9a8b9..99b7b387d 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/ReportGenerator.java @@ -26,6 +26,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -37,13 +38,16 @@ import org.apache.velocity.runtime.RuntimeConstants; import org.owasp.dependencycheck.analyzer.Analyzer; import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.exception.ReportException; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The ReportGenerator is used to, as the name implies, generate reports. Internally the generator uses the Velocity - * Templating Engine. The ReportGenerator exposes a list of Dependencies to the template when generating the report. + * The ReportGenerator is used to, as the name implies, generate reports. + * Internally the generator uses the Velocity Templating Engine. The + * ReportGenerator exposes a list of Dependencies to the template when + * generating the report. * * @author Jeremy Long */ @@ -79,7 +83,7 @@ public class ReportGenerator { /** * The Velocity Engine. */ - private final VelocityEngine engine; + private final VelocityEngine velocityEngine; /** * The Velocity Engine Context. */ @@ -91,13 +95,14 @@ public class ReportGenerator { * @param applicationName the application name being analyzed * @param dependencies the list of dependencies * @param analyzers the list of analyzers used - * @param properties the database properties (containing timestamps of the NVD CVE data) + * @param properties the database properties (containing timestamps of the + * NVD CVE data) */ public ReportGenerator(String applicationName, List dependencies, List analyzers, DatabaseProperties properties) { - engine = createVelocityEngine(); + velocityEngine = createVelocityEngine(); context = createContext(); - engine.init(); + velocityEngine.init(); final DateFormat dateFormat = new SimpleDateFormat("MMM d, yyyy 'at' HH:mm:ss z"); final DateFormat dateFormatXML = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); @@ -119,19 +124,19 @@ public class ReportGenerator { /** * Creates a new Velocity Engine. * - * @return a velocity engine. + * @return a velocity engine */ private VelocityEngine createVelocityEngine() { - final VelocityEngine engine = new VelocityEngine(); + final VelocityEngine velocity = new VelocityEngine(); // Logging redirection for Velocity - Required by Jenkins and other server applications - engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, VelocityLoggerRedirect.class.getName()); - return engine; + velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, VelocityLoggerRedirect.class.getName()); + return velocity; } /** * Creates a new Velocity Context. * - * @return a Velocity Context. + * @return a Velocity Context */ private Context createContext() { return new VelocityContext(); @@ -143,7 +148,7 @@ public class ReportGenerator { * @param outputStream the OutputStream to send the generated report to * @param format the format the report should be written in * @throws IOException is thrown when the template file does not exist - * @throws Exception is thrown if there is an error writing out the reports. + * @throws Exception is thrown if there is an error writing out the reports */ public void generateReports(OutputStream outputStream, Format format) throws IOException, Exception { if (format == Format.XML || format == Format.ALL) { @@ -162,10 +167,9 @@ public class ReportGenerator { * * @param outputDir the path where the reports should be written * @param format the format the report should be written in - * @throws IOException is thrown when the template file does not exist - * @throws Exception is thrown if there is an error writing out the reports. + * @throws ReportException is thrown if there is an error writing out the reports */ - public void generateReports(String outputDir, Format format) throws IOException, Exception { + public void generateReports(String outputDir, Format format) throws ReportException { if (format == Format.XML || format == Format.ALL) { generateReport("XmlReport", outputDir + File.separator + "dependency-check-report.xml"); } @@ -181,11 +185,12 @@ public class ReportGenerator { * Generates the Dependency Reports for the identified dependencies. * * @param outputDir the path where the reports should be written - * @param outputFormat the format the report should be written in (XML, HTML, ALL) - * @throws IOException is thrown when the template file does not exist - * @throws Exception is thrown if there is an error writing out the reports. + * @param outputFormat the format the report should be written in (XML, + * HTML, ALL) + * @throws ReportException is thrown if there is an error creating out the + * reports */ - public void generateReports(String outputDir, String outputFormat) throws IOException, Exception { + public void generateReports(String outputDir, String outputFormat) throws ReportException { final String format = outputFormat.toUpperCase(); final String pathToCheck = outputDir.toLowerCase(); if (format.matches("^(XML|HTML|VULN|ALL)$")) { @@ -217,16 +222,16 @@ public class ReportGenerator { } /** - * Generates a report from a given Velocity Template. The template name provided can be the name of a template - * contained in the jar file, such as 'XmlReport' or 'HtmlReport', or the template name can be the path to a + * Generates a report from a given Velocity Template. The template name + * provided can be the name of a template contained in the jar file, such as + * 'XmlReport' or 'HtmlReport', or the template name can be the path to a * template file. * - * @param templateName the name of the template to load. - * @param outputStream the OutputStream to write the report to. - * @throws IOException is thrown when the template file does not exist. - * @throws Exception is thrown when an exception occurs. + * @param templateName the name of the template to load + * @param outputStream the OutputStream to write the report to + * @throws ReportException is thrown when an exception occurs */ - protected void generateReport(String templateName, OutputStream outputStream) throws IOException, Exception { + protected void generateReport(String templateName, OutputStream outputStream) throws ReportException { InputStream input = null; String templatePath = null; final File f = new File(templateName); @@ -235,27 +240,30 @@ public class ReportGenerator { templatePath = templateName; input = new FileInputStream(f); } catch (FileNotFoundException ex) { - LOGGER.error("Unable to generate the report, the report template file could not be found."); - LOGGER.debug("", ex); + throw new ReportException("Unable to locate template file: " + templateName, ex); } } else { templatePath = "templates/" + templateName + ".vsl"; input = this.getClass().getClassLoader().getResourceAsStream(templatePath); } if (input == null) { - throw new IOException("Template file doesn't exist"); + throw new ReportException("Template file doesn't exist: " + templatePath); } - final InputStreamReader reader = new InputStreamReader(input, "UTF-8"); + InputStreamReader reader = null; OutputStreamWriter writer = null; try { + reader = new InputStreamReader(input, "UTF-8"); writer = new OutputStreamWriter(outputStream, "UTF-8"); - - if (!engine.evaluate(context, writer, templatePath, reader)) { - throw new Exception("Failed to convert the template into html."); + if (!velocityEngine.evaluate(context, writer, templatePath, reader)) { + throw new ReportException("Failed to convert the template into html."); } writer.flush(); + } catch (UnsupportedEncodingException ex) { + throw new ReportException("Unable to generate the report using UTF-8", ex); + } catch (IOException ex) { + throw new ReportException("Unable to write the report", ex); } finally { if (writer != null) { try { @@ -271,25 +279,27 @@ public class ReportGenerator { LOGGER.trace("", ex); } } - try { - reader.close(); - } catch (IOException ex) { - LOGGER.trace("", ex); + if (reader != null) { + try { + reader.close(); + } catch (IOException ex) { + LOGGER.trace("", ex); + } } } } /** - * Generates a report from a given Velocity Template. The template name provided can be the name of a template - * contained in the jar file, such as 'XmlReport' or 'HtmlReport', or the template name can be the path to a + * Generates a report from a given Velocity Template. The template name + * provided can be the name of a template contained in the jar file, such as + * 'XmlReport' or 'HtmlReport', or the template name can be the path to a * template file. * - * @param templateName the name of the template to load. - * @param outFileName the filename and path to write the report to. - * @throws IOException is thrown when the template file does not exist. - * @throws Exception is thrown when an exception occurs. + * @param templateName the name of the template to load + * @param outFileName the filename and path to write the report to + * @throws ReportException is thrown when the report cannot be generated */ - protected void generateReport(String templateName, String outFileName) throws Exception { + protected void generateReport(String templateName, String outFileName) throws ReportException { File outFile = new File(outFileName); if (outFile.getParentFile() == null) { outFile = new File(".", outFileName); @@ -297,7 +307,7 @@ public class ReportGenerator { if (!outFile.getParentFile().exists()) { final boolean created = outFile.getParentFile().mkdirs(); if (!created) { - throw new Exception("Unable to create directory '" + outFile.getParentFile().getAbsolutePath() + "'."); + throw new ReportException("Unable to create directory '" + outFile.getParentFile().getAbsolutePath() + "'."); } } @@ -305,6 +315,8 @@ public class ReportGenerator { try { outputSteam = new FileOutputStream(outFile); generateReport(templateName, outputSteam); + } catch (FileNotFoundException ex) { + throw new ReportException("Unable to write to file: " + outFile, ex); } finally { if (outputSteam != null) { try { diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java index 483413dcb..61e88a820 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersionUtil.java @@ -24,7 +24,8 @@ import java.util.regex.Pattern; /** *

- * A utility class to extract version numbers from file names (or other strings containing version numbers.

+ * A utility class to extract version numbers from file names (or other strings + * containing version numbers.

* * @author Jeremy Long */ @@ -35,11 +36,19 @@ public final class DependencyVersionUtil { */ private static final Pattern RX_VERSION = Pattern.compile("\\d+(\\.\\d{1,6})+(\\.?([_-](release|beta|alpha|\\d+)|[a-zA-Z_-]{1,3}\\d{0,8}))?"); /** - * Regular expression to extract a single version number without periods. This is a last ditch effort just to check in case we - * are missing a version number using the previous regex. + * Regular expression to extract a single version number without periods. + * This is a last ditch effort just to check in case we are missing a + * version number using the previous regex. */ private static final Pattern RX_SINGLE_VERSION = Pattern.compile("\\d+(\\.?([_-](release|beta|alpha)|[a-zA-Z_-]{1,3}\\d{1,8}))?"); + /** + * Regular expression to extract the part before the version numbers if + * there are any based on RX_VERSION. In most cases, this part represents a + * more accurate name. + */ + private static final Pattern RX_PRE_VERSION = Pattern.compile("^(.+)[_-](\\d+\\.\\d{1,6})+"); + /** * Private constructor for utility class. */ @@ -48,7 +57,8 @@ public final class DependencyVersionUtil { /** *

- * A utility class to extract version numbers from file names (or other strings containing version numbers.

+ * A utility class to extract version numbers from file names (or other + * strings containing version numbers.

*
      * Example:
      * Give the file name: library-name-1.4.1r2-release.jar
@@ -95,4 +105,30 @@ public final class DependencyVersionUtil {
         }
         return new DependencyVersion(version);
     }
+
+    /**
+     * 

+ * A utility class to extract the part before version numbers from file + * names (or other strings containing version numbers. In most cases, this + * part represents a more accurate name than the full file name.

+ *
+     * Example:
+     * Give the file name: library-name-1.4.1r2-release.jar
+     * This function would return: library-name
+ * + * @param text the text being analyzed + * @return the part before the version numbers if any, otherwise return the + * text itself. + */ + public static String parsePreVersion(String text) { + if (parseVersion(text) == null) { + return text; + } + + final Matcher matcher = RX_PRE_VERSION.matcher(text); + if (matcher.find()) { + return matcher.group(1); + } + return text; + } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintHandler.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintHandler.java index bd1477e63..0608f5fa1 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintHandler.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintHandler.java @@ -20,7 +20,7 @@ package org.owasp.dependencycheck.xml.hints; import java.util.ArrayList; import java.util.List; import org.owasp.dependencycheck.dependency.Confidence; -import org.owasp.dependencycheck.suppression.PropertyType; +import org.owasp.dependencycheck.xml.suppression.PropertyType; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -62,9 +62,17 @@ public class HintHandler extends DefaultHandler { */ private static final String DUPLICATE = "duplicate"; /** - * Attribute name. + * Attribute value. */ private static final String VENDOR = "vendor"; + /** + * Attribute value. + */ + private static final String PRODUCT = "product"; + /** + * Attribute value. + */ + private static final String VERSION = "version"; /** * Attribute name. */ @@ -168,16 +176,25 @@ public class HintHandler extends DefaultHandler { attr.getValue(VALUE), Confidence.valueOf(attr.getValue(CONFIDENCE))); } - } else if (inAddNode) { - rule.addAddProduct(attr.getValue(SOURCE), - attr.getValue(NAME), - attr.getValue(VALUE), - Confidence.valueOf(attr.getValue(CONFIDENCE))); - } else { - rule.addGivenProduct(attr.getValue(SOURCE), - attr.getValue(NAME), - attr.getValue(VALUE), - Confidence.valueOf(attr.getValue(CONFIDENCE))); + } else if (PRODUCT.equals(hintType)) { + if (inAddNode) { + rule.addAddProduct(attr.getValue(SOURCE), + attr.getValue(NAME), + attr.getValue(VALUE), + Confidence.valueOf(attr.getValue(CONFIDENCE))); + } else { + rule.addGivenProduct(attr.getValue(SOURCE), + attr.getValue(NAME), + attr.getValue(VALUE), + Confidence.valueOf(attr.getValue(CONFIDENCE))); + } + } else if (VERSION.equals(hintType)) { + if (inAddNode) { + rule.addAddVersion(attr.getValue(SOURCE), + attr.getValue(NAME), + attr.getValue(VALUE), + Confidence.valueOf(attr.getValue(CONFIDENCE))); + } } } else if (FILE_NAME.equals(qName)) { final PropertyType pt = new PropertyType(); @@ -197,11 +214,11 @@ public class HintHandler extends DefaultHandler { vendorDuplicatingHintRules.add(new VendorDuplicatingHintRule(attr.getValue(VALUE), attr.getValue(DUPLICATE))); } } - + /** * Handles the end element event. * - * @param uri the element's uri + * @param uri the element's URI * @param localName the local name * @param qName the qualified name * @throws SAXException thrown if there is an exception processing the diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintParser.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintParser.java index 5eb0aa6bb..96a35bdc9 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintParser.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintParser.java @@ -64,7 +64,7 @@ public class HintParser { /** * The schema for the hint XML files. */ - private static final String HINT_SCHEMA = "schema/dependency-hint.1.0.xsd"; + private static final String HINT_SCHEMA = "schema/dependency-hint.1.1.xsd"; /** * Parses the given XML file and returns a list of the hints contained. @@ -104,8 +104,9 @@ public class HintParser { * @throws SAXException thrown if the XML cannot be parsed */ public Hints parseHints(InputStream inputStream) throws HintParseException, SAXException { + InputStream schemaStream = null; try { - final InputStream schemaStream = this.getClass().getClassLoader().getResourceAsStream(HINT_SCHEMA); + schemaStream = this.getClass().getClassLoader().getResourceAsStream(HINT_SCHEMA); final HintHandler handler = new HintHandler(); final SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); @@ -141,6 +142,14 @@ public class HintParser { } catch (IOException ex) { LOGGER.debug("", ex); throw new HintParseException(ex); + } finally { + if (schemaStream != null) { + try { + schemaStream.close(); + } catch (IOException ex) { + LOGGER.debug("Error closing hint file stream", ex); + } + } } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintRule.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintRule.java index 0ab10091d..7290ba26e 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintRule.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintRule.java @@ -21,7 +21,7 @@ import java.util.ArrayList; import java.util.List; import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Evidence; -import org.owasp.dependencycheck.suppression.PropertyType; +import org.owasp.dependencycheck.xml.suppression.PropertyType; /** * A collection of product and vendor evidence to match; if any evidence is @@ -72,9 +72,9 @@ public class HintRule { } /** - * Get the value of givenProduct + * Get the value of givenProduct. * - * @return the value of givenProduct. + * @return the value of givenProduct */ public List getGivenProduct() { return givenProduct; @@ -85,6 +85,15 @@ public class HintRule { */ private final List givenVendor = new ArrayList(); + /** + * The list of product evidence to add. + */ + private final List addProduct = new ArrayList(); + /** + * The list of version evidence to add. + */ + private final List addVersion = new ArrayList(); + /** * Adds a given vendors to the list of evidence to matched. * @@ -106,11 +115,6 @@ public class HintRule { return givenVendor; } - /** - * The list of product evidence to add. - */ - private final List addProduct = new ArrayList(); - /** * Adds a given product to the list of evidence to add when matched. * @@ -132,6 +136,27 @@ public class HintRule { return addProduct; } + /** + * Adds a given version to the list of evidence to add when matched. + * + * @param source the source of the evidence + * @param name the name of the evidence + * @param value the value of the evidence + * @param confidence the confidence of the evidence + */ + public void addAddVersion(String source, String name, String value, Confidence confidence) { + addVersion.add(new Evidence(source, name, value, confidence)); + } + + /** + * Get the value of addVersion. + * + * @return the value of addVersion + */ + public List getAddVersion() { + return addVersion; + } + /** * The list of vendor hints to add. */ diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/Hints.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/Hints.java index 0240d2fc1..34e465004 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/Hints.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/Hints.java @@ -32,7 +32,7 @@ public class Hints { private List hintRules; /** - * Get the value of hintRules + * Get the value of hintRules. * * @return the value of hintRules */ @@ -41,7 +41,7 @@ public class Hints { } /** - * Set the value of hintRules + * Set the value of hintRules. * * @param hintRules new value of hintRules */ @@ -55,7 +55,7 @@ public class Hints { private List vendorDuplicatingHintRules; /** - * Get the value of vendorDuplicatingHintRules + * Get the value of vendorDuplicatingHintRules. * * @return the value of vendorDuplicatingHintRules */ @@ -64,12 +64,11 @@ public class Hints { } /** - * Set the value of vendorDuplicatingHintRules + * Set the value of vendorDuplicatingHintRules. * * @param vendorDuplicatingHintRules new value of vendorDuplicatingHintRules */ public void setVendorDuplicatingHintRules(List vendorDuplicatingHintRules) { this.vendorDuplicatingHintRules = vendorDuplicatingHintRules; } - } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/pom/PomUtils.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/pom/PomUtils.java index f3ea0b0e9..6f3aa4987 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/pom/PomUtils.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/pom/PomUtils.java @@ -48,13 +48,17 @@ public final class PomUtils { * * @param file the pom.xml file * @return returns a - * @throws AnalysisException is thrown if there is an exception extracting or parsing the POM {@link Model} object + * @throws AnalysisException is thrown if there is an exception extracting + * or parsing the POM {@link Model} object */ public static Model readPom(File file) throws AnalysisException { - Model model = null; try { final PomParser parser = new PomParser(); - model = parser.parse(file); + final Model model = parser.parse(file); + if (model == null) { + throw new AnalysisException(String.format("Unable to parse pom '%s'", file.getPath())); + } + return model; } catch (PomParseException ex) { LOGGER.warn("Unable to parse pom '{}'", file.getPath()); LOGGER.debug("", ex); @@ -68,7 +72,6 @@ public final class PomUtils { LOGGER.debug("", ex); throw new AnalysisException(ex); } - return model; } /** @@ -77,7 +80,8 @@ public final class PomUtils { * @param path the path to the pom.xml file within the jar file * @param jar the jar file to extract the pom from * @return returns a - * @throws AnalysisException is thrown if there is an exception extracting or parsing the POM {@link Model} object + * @throws AnalysisException is thrown if there is an exception extracting + * or parsing the POM {@link Model} object */ public static Model readPom(String path, JarFile jar) throws AnalysisException { final ZipEntry entry = jar.getEntry(path); @@ -86,7 +90,9 @@ public final class PomUtils { try { final PomParser parser = new PomParser(); model = parser.parse(jar.getInputStream(entry)); - LOGGER.debug("Read POM {}", path); + if (model == null) { + throw new AnalysisException(String.format("Unable to parse pom '%s/%s'", jar.getName(), path)); + } } catch (SecurityException ex) { LOGGER.warn("Unable to parse pom '{}' in jar '{}'; invalid signature", path, jar.getName()); LOGGER.debug("", ex); @@ -105,11 +111,13 @@ public final class PomUtils { } /** - * Reads in the pom file and adds elements as evidence to the given dependency. + * Reads in the pom file and adds elements as evidence to the given + * dependency. * * @param dependency the dependency being analyzed * @param pomFile the pom file to read - * @throws AnalysisException is thrown if there is an exception parsing the pom + * @throws AnalysisException is thrown if there is an exception parsing the + * pom */ public static void analyzePOM(Dependency dependency, File pomFile) throws AnalysisException { final Model pom = PomUtils.readPom(pomFile); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/PropertyType.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/PropertyType.java similarity index 98% rename from dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/PropertyType.java rename to dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/PropertyType.java index 4ecd66323..772f338fb 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/PropertyType.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/PropertyType.java @@ -15,7 +15,7 @@ * * Copyright (c) 2013 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; import java.util.regex.Pattern; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionErrorHandler.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionErrorHandler.java similarity index 91% rename from dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionErrorHandler.java rename to dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionErrorHandler.java index 5d8b8733a..07d4b661e 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionErrorHandler.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionErrorHandler.java @@ -15,10 +15,8 @@ * * Copyright (c) 2013 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; @@ -33,7 +31,7 @@ public class SuppressionErrorHandler implements ErrorHandler { /** * The logger. */ - private static final Logger LOGGER = LoggerFactory.getLogger(SuppressionErrorHandler.class); + //private static final Logger LOGGER = LoggerFactory.getLogger(SuppressionErrorHandler.class); /** * Builds a prettier exception message. @@ -70,7 +68,7 @@ public class SuppressionErrorHandler implements ErrorHandler { */ @Override public void warning(SAXParseException ex) throws SAXException { - LOGGER.debug("", ex); + //LOGGER.debug("", ex); } /** diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionHandler.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionHandler.java similarity index 99% rename from dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionHandler.java rename to dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionHandler.java index ddb414e6f..b07909653 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionHandler.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionHandler.java @@ -15,7 +15,7 @@ * * Copyright (c) 2013 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; import java.util.ArrayList; import java.util.List; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionParseException.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionParseException.java similarity index 97% rename from dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionParseException.java rename to dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionParseException.java index 6c8e938de..d0d5c87df 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionParseException.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionParseException.java @@ -15,7 +15,7 @@ * * Copyright (c) 2013 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; import java.io.IOException; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionParser.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionParser.java similarity index 89% rename from dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionParser.java rename to dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionParser.java index 945388a7a..d6e863f55 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionParser.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionParser.java @@ -15,7 +15,7 @@ * * Copyright (c) 2013 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; import java.io.File; import java.io.FileInputStream; @@ -121,8 +121,9 @@ public class SuppressionParser { * @throws SAXException thrown if the XML cannot be parsed */ public List parseSuppressionRules(InputStream inputStream) throws SuppressionParseException, SAXException { + InputStream schemaStream = null; try { - final InputStream schemaStream = this.getClass().getClassLoader().getResourceAsStream(SUPPRESSION_SCHEMA); + schemaStream = this.getClass().getClassLoader().getResourceAsStream(SUPPRESSION_SCHEMA); final SuppressionHandler handler = new SuppressionHandler(); final SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); @@ -157,6 +158,14 @@ public class SuppressionParser { } catch (IOException ex) { LOGGER.debug("", ex); throw new SuppressionParseException(ex); + } finally { + if (schemaStream != null) { + try { + schemaStream.close(); + } catch (IOException ex) { + LOGGER.debug("Error closing suppression file stream", ex); + } + } } } @@ -169,8 +178,9 @@ public class SuppressionParser { * @throws SuppressionParseException if the XML cannot be parsed */ private List parseOldSuppressionRules(InputStream inputStream) throws SuppressionParseException { + InputStream schemaStream = null; try { - final InputStream schemaStream = this.getClass().getClassLoader().getResourceAsStream(OLD_SUPPRESSION_SCHEMA); + schemaStream = this.getClass().getClassLoader().getResourceAsStream(OLD_SUPPRESSION_SCHEMA); final SuppressionHandler handler = new SuppressionHandler(); final SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); @@ -200,6 +210,14 @@ public class SuppressionParser { } catch (IOException ex) { LOGGER.debug("", ex); throw new SuppressionParseException(ex); + } finally { + if (schemaStream != null) { + try { + schemaStream.close(); + } catch (IOException ex) { + LOGGER.debug("Error closing old suppression file stream", ex); + } + } } } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionRule.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionRule.java similarity index 99% rename from dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionRule.java rename to dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionRule.java index 1a2353fdf..80e78bdd2 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/SuppressionRule.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionRule.java @@ -15,7 +15,7 @@ * * Copyright (c) 2013 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; import java.util.ArrayList; import java.util.Iterator; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/package-info.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/package-info.java similarity index 51% rename from dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/package-info.java rename to dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/package-info.java index f8974043d..0abc2af40 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/suppression/package-info.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/suppression/package-info.java @@ -1,4 +1,4 @@ /** * Contains classes used to suppress findings. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; diff --git a/dependency-check-core/src/main/resources/GrokAssembly.exe b/dependency-check-core/src/main/resources/GrokAssembly.exe index 5b2104a54..a68ce50e1 100644 Binary files a/dependency-check-core/src/main/resources/GrokAssembly.exe and b/dependency-check-core/src/main/resources/GrokAssembly.exe differ diff --git a/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer b/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer index 7f67a3d84..674d1d0f7 100644 --- a/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer +++ b/dependency-check-core/src/main/resources/META-INF/services/org.owasp.dependencycheck.analyzer.Analyzer @@ -22,3 +22,5 @@ org.owasp.dependencycheck.analyzer.RubyGemspecAnalyzer org.owasp.dependencycheck.analyzer.RubyBundlerAnalyzer org.owasp.dependencycheck.analyzer.RubyBundleAuditAnalyzer org.owasp.dependencycheck.analyzer.ComposerLockAnalyzer +org.owasp.dependencycheck.analyzer.CocoaPodsAnalyzer +org.owasp.dependencycheck.analyzer.SwiftPackageManagerAnalyzer diff --git a/dependency-check-core/src/main/resources/dependencycheck-base-hint.xml b/dependency-check-core/src/main/resources/dependencycheck-base-hint.xml index 4e1d870a6..5d3dacdaa 100644 --- a/dependency-check-core/src/main/resources/dependencycheck-base-hint.xml +++ b/dependency-check-core/src/main/resources/dependencycheck-base-hint.xml @@ -1,75 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml index 97c9b0967..b06cc702d 100644 --- a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml +++ b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml @@ -410,7 +410,7 @@ - org.eclipse.aether:aether.* + org\.eclipse\.aether:aether.* cpe:/a:eclipse:eclipse_ide @@ -420,4 +420,11 @@ .*\.(jar|ear|war|pom) cpe:/a:services_project:services + + + com\.offbytwo\.jenkins:jenkins-client:.* + cpe:/a:jenkins:jenkins + diff --git a/dependency-check-core/src/main/resources/dependencycheck.properties b/dependency-check-core/src/main/resources/dependencycheck.properties index def5b8d86..fe567580e 100644 --- a/dependency-check-core/src/main/resources/dependencycheck.properties +++ b/dependency-check-core/src/main/resources/dependencycheck.properties @@ -96,6 +96,8 @@ analyzer.nuspec.enabled=true analyzer.openssl.enabled=true analyzer.central.enabled=true analyzer.nexus.enabled=false +analyzer.cocoapods.enabled=true +analyzer.swift.package.manager.enabled=true #whether the nexus analyzer uses the proxy analyzer.nexus.proxy=true diff --git a/dependency-check-core/src/main/resources/schema/dependency-hint.1.1.xsd b/dependency-check-core/src/main/resources/schema/dependency-hint.1.1.xsd new file mode 100644 index 000000000..63390044a --- /dev/null +++ b/dependency-check-core/src/main/resources/schema/dependency-hint.1.1.xsd @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/EngineIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/EngineIntegrationTest.java index 1e12e6a82..bceefc23b 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/EngineIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/EngineIntegrationTest.java @@ -17,12 +17,19 @@ */ package org.owasp.dependencycheck; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; import static org.junit.Assert.assertTrue; import org.junit.Test; import org.owasp.dependencycheck.data.nvdcve.CveDB; +import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties; +import org.owasp.dependencycheck.exception.ExceptionCollection; +import org.owasp.dependencycheck.exception.ReportException; import org.owasp.dependencycheck.reporting.ReportGenerator; +import org.owasp.dependencycheck.utils.InvalidSettingException; import org.owasp.dependencycheck.utils.Settings; /** @@ -34,10 +41,14 @@ public class EngineIntegrationTest extends BaseDBTestCase { /** * Test running the entire engine. * - * @throws Exception is thrown when an exception occurs. + * @throws java.io.IOException + * @throws org.owasp.dependencycheck.utils.InvalidSettingException + * @throws org.owasp.dependencycheck.data.nvdcve.DatabaseException + * @throws org.owasp.dependencycheck.exception.ReportException + * @throws org.owasp.dependencycheck.exception.ExceptionCollection */ @Test - public void testEngine() throws Exception { + public void testEngine() throws IOException, InvalidSettingException, DatabaseException, ReportException, ExceptionCollection { String testClasses = "target/test-classes"; boolean autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); @@ -45,7 +56,23 @@ public class EngineIntegrationTest extends BaseDBTestCase { Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate); instance.scan(testClasses); assertTrue(instance.getDependencies().size() > 0); - instance.analyzeDependencies(); + try { + instance.analyzeDependencies(); + } catch (ExceptionCollection ex) { + if (ex.getExceptions().size()==1 && + (ex.getExceptions().get(0).getMessage().contains("bundle-audit") || + ex.getExceptions().get(0).getMessage().contains("AssemblyAnalyzer"))) { + //this is fine to ignore + } else if (ex.getExceptions().size()==2 && + ((ex.getExceptions().get(0).getMessage().contains("bundle-audit") && + ex.getExceptions().get(1).getMessage().contains("AssemblyAnalyzer")) || + (ex.getExceptions().get(1).getMessage().contains("bundle-audit") && + ex.getExceptions().get(0).getMessage().contains("AssemblyAnalyzer")))) { + //this is fine to ignore + } else { + throw ex; + } + } CveDB cveDB = new CveDB(); cveDB.open(); DatabaseProperties dbProp = cveDB.getDatabaseProperties(); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java index 27527b2c2..ddb075af2 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java @@ -23,8 +23,7 @@ import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.dependency.Dependency; -import org.owasp.dependencycheck.suppression.SuppressionParseException; -import org.owasp.dependencycheck.suppression.SuppressionRule; +import org.owasp.dependencycheck.xml.suppression.SuppressionRule; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.LoggerFactory; @@ -35,6 +34,7 @@ import java.util.Set; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import org.owasp.dependencycheck.exception.InitializationException; /** * @author Jeremy Long @@ -49,7 +49,8 @@ public class AbstractSuppressionAnalyzerTest extends BaseTest { } /** - * Test of getSupportedExtensions method, of class AbstractSuppressionAnalyzer. + * Test of getSupportedExtensions method, of class + * AbstractSuppressionAnalyzer. */ @Test public void testGetSupportedExtensions() { @@ -58,7 +59,8 @@ public class AbstractSuppressionAnalyzerTest extends BaseTest { } /** - * Test of getRules method, of class AbstractSuppressionAnalyzer for suppression file declared as URL. + * Test of getRules method, of class AbstractSuppressionAnalyzer for + * suppression file declared as URL. */ @Test public void testGetRulesFromSuppressionFileFromURL() throws Exception { @@ -70,7 +72,8 @@ public class AbstractSuppressionAnalyzerTest extends BaseTest { } /** - * Test of getRules method, of class AbstractSuppressionAnalyzer for suppression file declared as URL. + * Test of getRules method, of class AbstractSuppressionAnalyzer for + * suppression file declared as URL. */ @Test public void testGetRulesFromSuppressionFileInClasspath() throws Exception { @@ -81,7 +84,7 @@ public class AbstractSuppressionAnalyzerTest extends BaseTest { assertTrue(expCount <= currentSize); } - @Test(expected = SuppressionParseException.class) + @Test(expected = InitializationException.class) public void testFailureToLocateSuppressionFileAnywhere() throws Exception { Settings.setString(Settings.KEYS.SUPPRESSION_FILE, "doesnotexist.xml"); instance.initialize(); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzerTest.java index 6cdbae18a..d00d83285 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AssemblyAnalyzerTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Assume; import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeNotNull; import org.junit.Before; import org.junit.Test; import org.owasp.dependencycheck.BaseTest; @@ -31,6 +32,7 @@ import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Evidence; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,6 +62,7 @@ public class AssemblyAnalyzerTest extends BaseTest { analyzer = new AssemblyAnalyzer(); analyzer.accept(new File("test.dll")); // trick into "thinking it is active" analyzer.initialize(); + Assume.assumeTrue("Mono is not installed, skipping tests.", analyzer.buildArgumentList() == null); } catch (Exception e) { if (e.getMessage().contains("Could not execute .NET AssemblyAnalyzer")) { LOGGER.warn("Exception setting up AssemblyAnalyzer. Tests will be incomplete"); @@ -80,7 +83,7 @@ public class AssemblyAnalyzerTest extends BaseTest { @Test public void testAnalysis() throws Exception { - //File f = new File(AssemblyAnalyzerTest.class.getClassLoader().getResource("GrokAssembly.exe").getPath()); + assumeNotNull(analyzer.buildArgumentList()); File f = BaseTest.getResourceAsFile(this, "GrokAssembly.exe"); Dependency d = new Dependency(f); analyzer.analyze(d, null); @@ -103,7 +106,7 @@ public class AssemblyAnalyzerTest extends BaseTest { @Test public void testLog4Net() throws Exception { - //File f = new File(AssemblyAnalyzerTest.class.getClassLoader().getResource("log4net.dll").getPath()); + assumeNotNull(analyzer.buildArgumentList()); File f = BaseTest.getResourceAsFile(this, "log4net.dll"); Dependency d = new Dependency(f); @@ -115,9 +118,10 @@ public class AssemblyAnalyzerTest extends BaseTest { @Test public void testNonexistent() { + assumeNotNull(analyzer.buildArgumentList()); + // Tweak the log level so the warning doesn't show in the console String oldProp = System.getProperty(LOG_KEY, "info"); - //File f = new File(AssemblyAnalyzerTest.class.getClassLoader().getResource("log4net.dll").getPath()); File f = BaseTest.getResourceAsFile(this, "log4net.dll"); File test = new File(f.getParent(), "nonexistent.dll"); Dependency d = new Dependency(test); @@ -157,8 +161,8 @@ public class AssemblyAnalyzerTest extends BaseTest { AssemblyAnalyzer aanalyzer = new AssemblyAnalyzer(); aanalyzer.accept(new File("test.dll")); // trick into "thinking it is active" aanalyzer.initialize(); - fail("Expected an AnalysisException"); - } catch (AnalysisException ae) { + fail("Expected an InitializationException"); + } catch (InitializationException ae) { assertEquals("An error occurred with the .NET AssemblyAnalyzer", ae.getMessage()); } finally { System.setProperty(LOG_KEY, oldProp); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/CPEAnalyzerIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/CPEAnalyzerIntegrationTest.java index 552ec2abc..0eef5db5e 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/CPEAnalyzerIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/CPEAnalyzerIntegrationTest.java @@ -189,6 +189,7 @@ public class CPEAnalyzerIntegrationTest extends BaseDBTestCase { instance.determineCPE(spring); instance.determineCPE(spring3); instance.close(); + String expResult = "cpe:/a:apache:struts:2.1.2"; Identifier expIdentifier = new Identifier("cpe", expResult, expResult); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzerTest.java index d1f9101c3..c1b57ab66 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/RubyBundleAuditAnalyzerTest.java @@ -26,6 +26,7 @@ import java.io.File; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.logging.Level; import org.junit.After; import org.junit.Assume; @@ -40,6 +41,7 @@ import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Evidence; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.exception.ExceptionCollection; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -115,7 +117,6 @@ public class RubyBundleAuditAnalyzerTest extends BaseDBTestCase { final Engine engine = new Engine(); analyzer.analyze(result, engine); int size = engine.getDependencies().size(); - assertTrue(size >= 1); Dependency dependency = engine.getDependencies().get(0); @@ -176,6 +177,7 @@ public class RubyBundleAuditAnalyzerTest extends BaseDBTestCase { * Test Ruby dependencies and their paths. * * @throws AnalysisException is thrown when an exception occurs. + * @throws DatabaseException thrown when an exception occurs */ @Test public void testDependenciesPath() throws AnalysisException, DatabaseException { @@ -187,6 +189,8 @@ public class RubyBundleAuditAnalyzerTest extends BaseDBTestCase { } catch (NullPointerException ex) { LOGGER.error("NPE", ex); throw ex; + } catch (ExceptionCollection ex) { + Assume.assumeNoException("Exception setting up RubyBundleAuditAnalyzer; bundle audit may not be installed, or property \"analyzer.bundle.audit.path\" may not be set.", ex); } List dependencies = engine.getDependencies(); LOGGER.info(dependencies.size() + " dependencies found."); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/SwiftAnalyzersTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/SwiftAnalyzersTest.java new file mode 100644 index 000000000..94e4b020d --- /dev/null +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/SwiftAnalyzersTest.java @@ -0,0 +1,123 @@ +package org.owasp.dependencycheck.analyzer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.owasp.dependencycheck.BaseTest; +import org.owasp.dependencycheck.analyzer.exception.AnalysisException; +import org.owasp.dependencycheck.dependency.Dependency; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.io.File; + +/** + * Unit tests for CocoaPodsAnalyzer. + * + * @author Bianca Jiang + */ +public class SwiftAnalyzersTest extends BaseTest { + + /** + * The analyzer to test. + */ + CocoaPodsAnalyzer podsAnalyzer; + SwiftPackageManagerAnalyzer spmAnalyzer; + + /** + * Correctly setup the analyzer for testing. + * + * @throws Exception thrown if there is a problem + */ + @Before + public void setUp() throws Exception { + podsAnalyzer = new CocoaPodsAnalyzer(); + podsAnalyzer.setFilesMatched(true); + podsAnalyzer.initialize(); + + spmAnalyzer = new SwiftPackageManagerAnalyzer(); + spmAnalyzer.setFilesMatched(true); + spmAnalyzer.initialize(); + } + + /** + * Cleanup the analyzer's temp files, etc. + * + * @throws Exception thrown if there is a problem + */ + @After + public void tearDown() throws Exception { + podsAnalyzer.close(); + podsAnalyzer = null; + + spmAnalyzer.close(); + spmAnalyzer = null; + } + + /** + * Test of getName method, of class CocoaPodsAnalyzer. + */ + @Test + public void testPodsGetName() { + assertThat(podsAnalyzer.getName(), is("CocoaPods Package Analyzer")); + } + + /** + * Test of getName method, of class SwiftPackageManagerAnalyzer. + */ + @Test + public void testSPMGetName() { + assertThat(spmAnalyzer.getName(), is("SWIFT Package Manager Analyzer")); + } + + /** + * Test of supportsFiles method, of class CocoaPodsAnalyzer. + */ + @Test + public void testPodsSupportsFiles() { + assertThat(podsAnalyzer.accept(new File("test.podspec")), is(true)); + } + + /** + * Test of supportsFiles method, of class SwiftPackageManagerAnalyzer. + */ + @Test + public void testSPMSupportsFiles() { + assertThat(spmAnalyzer.accept(new File("Package.swift")), is(true)); + } + + /** + * Test of analyze method, of class CocoaPodsAnalyzer. + * + * @throws AnalysisException is thrown when an exception occurs. + */ + @Test + public void testCocoaPodsAnalyzer() throws AnalysisException { + final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, + "swift/cocoapods/EasyPeasy.podspec")); + podsAnalyzer.analyze(result, null); + final String vendorString = result.getVendorEvidence().toString(); + + assertThat(vendorString, containsString("Carlos Vidal")); + assertThat(vendorString, containsString("https://github.com/nakiostudio/EasyPeasy")); + assertThat(vendorString, containsString("MIT")); + assertThat(result.getProductEvidence().toString(), containsString("EasyPeasy")); + assertThat(result.getVersionEvidence().toString(), containsString("0.2.3")); + } + + /** + * Test of analyze method, of class SwiftPackageManagerAnalyzer. + * + * @throws AnalysisException is thrown when an exception occurs. + */ + @Test + public void testSPMAnalyzer() throws AnalysisException { + final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, + "swift/Gloss/Package.swift")); + spmAnalyzer.analyze(result, null); + + assertThat(result.getProductEvidence().toString(), containsString("Gloss")); + } +} diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/NvdCveUpdaterIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/NvdCveUpdaterIntegrationTest.java index 8f43f1a09..d666c9772 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/NvdCveUpdaterIntegrationTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/NvdCveUpdaterIntegrationTest.java @@ -40,12 +40,11 @@ public class NvdCveUpdaterIntegrationTest extends BaseTest { // /** // * Test of update method, of class StandardUpdate. // */ -// @Test -// public void testUpdate() throws Exception { -// StandardUpdate instance = getStandardUpdateTask(); -// instance.update(); -// //TODO make this an actual test -// } + @Test + public void testUpdate() throws Exception { + NvdCveUpdater instance = getUpdater(); + instance.update(); + } /** * Test of updatesNeeded method, of class StandardUpdate. */ diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/nvd/NvdCveUpdaterIntegrationTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/nvd/NvdCveUpdaterIntegrationTest.java deleted file mode 100644 index d93fbce6b..000000000 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/update/nvd/NvdCveUpdaterIntegrationTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is part of dependency-check-core. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Copyright (c) 2013 Jeremy Long. All Rights Reserved. - */ -package org.owasp.dependencycheck.data.update.nvd; - -import java.io.File; -import java.util.Calendar; -import org.junit.Before; -import org.junit.Test; -import org.owasp.dependencycheck.BaseTest; -import org.owasp.dependencycheck.data.update.NvdCveUpdater; -import org.owasp.dependencycheck.utils.Settings; - -/** - * - * @author Jeremy Long - */ -public class NvdCveUpdaterIntegrationTest extends BaseTest { - - @Before - public void setUp() throws Exception { - int year = Calendar.getInstance().get(Calendar.YEAR); - if (year <= 2014) { - //File f = new File(NvdCveUpdaterIntegrationTest.class.getClassLoader().getResource("nvdcve-2.0-2014.xml").getPath()); - File f = BaseTest.getResourceAsFile(this, "nvdcve-2.0-2014.xml"); - String baseURL = f.toURI().toURL().toString(); - String modified12 = baseURL.replace("nvdcve-2.0-2014.xml", "nvdcve-modified.xml"); - String modified20 = baseURL.replace("nvdcve-2.0-2014.xml", "nvdcve-2.0-modified.xml"); - String full12 = baseURL.replace("nvdcve-2.0-2014.xml", "nvdcve-%d.xml"); - String full20 = baseURL.replace("nvdcve-2.0-2014.xml", "nvdcve-2.0-%d.xml"); -// cve.url-1.2.modified=http://nvd.nist.gov/download/nvdcve-modified.xml -// cve.url-2.0.modified=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-modified.xml -// cve.startyear=2014 -// cve.url-2.0.base=http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml -// cve.url-1.2.base=http://nvd.nist.gov/download/nvdcve-%d.xml - - Settings.setString(Settings.KEYS.CVE_MODIFIED_12_URL, modified12); - Settings.setString(Settings.KEYS.CVE_MODIFIED_20_URL, modified20); - Settings.setString(Settings.KEYS.CVE_SCHEMA_1_2, full12); - Settings.setString(Settings.KEYS.CVE_SCHEMA_2_0, full20); - Settings.setString(Settings.KEYS.CVE_START_YEAR, "2014"); - } else { - System.err.println("Consider updating the local data files to make the NvdCveUpdaterIntegrationTest perform faster"); - } - } - - /** - * Test of update method, of class NvdCveUpdater. - */ - @Test - public void testUpdate() throws Exception { - NvdCveUpdater instance = new NvdCveUpdater(); - instance.update(); - } -} diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/HintHandlerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/HintHandlerTest.java index dd71ebf4b..2d06c3a69 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/HintHandlerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/HintHandlerTest.java @@ -32,10 +32,10 @@ import javax.xml.parsers.SAXParserFactory; import org.junit.Test; import static org.junit.Assert.*; import org.owasp.dependencycheck.BaseTest; -import org.owasp.dependencycheck.suppression.SuppressionErrorHandler; -import org.owasp.dependencycheck.suppression.SuppressionHandler; -import org.owasp.dependencycheck.suppression.SuppressionParser; -import org.owasp.dependencycheck.suppression.SuppressionRule; +import org.owasp.dependencycheck.xml.suppression.SuppressionErrorHandler; +import org.owasp.dependencycheck.xml.suppression.SuppressionHandler; +import org.owasp.dependencycheck.xml.suppression.SuppressionParser; +import org.owasp.dependencycheck.xml.suppression.SuppressionRule; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -52,7 +52,7 @@ public class HintHandlerTest extends BaseTest { @Test public void testHandler() throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException, SAXException, FileNotFoundException, UnsupportedEncodingException, IOException { File file = BaseTest.getResourceAsFile(this, "hints.xml"); - File schema = BaseTest.getResourceAsFile(this, "schema/dependency-hint.1.0.xsd"); + File schema = BaseTest.getResourceAsFile(this, "schema/dependency-hint.1.1.xsd"); HintHandler handler = new HintHandler(); SAXParserFactory factory = SAXParserFactory.newInstance(); diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/PropertyTypeTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/PropertyTypeTest.java similarity index 98% rename from dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/PropertyTypeTest.java rename to dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/PropertyTypeTest.java index 2502ee4fd..9ad49e0f1 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/PropertyTypeTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/PropertyTypeTest.java @@ -15,7 +15,7 @@ * * Copyright (c) 2013 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionHandlerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/SuppressionHandlerTest.java similarity index 98% rename from dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionHandlerTest.java rename to dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/SuppressionHandlerTest.java index 45fd9240b..9517a1a7f 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionHandlerTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/SuppressionHandlerTest.java @@ -15,7 +15,7 @@ * * Copyright (c) 2013 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; import java.io.File; import java.io.FileInputStream; diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionParserTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/SuppressionParserTest.java similarity index 96% rename from dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionParserTest.java rename to dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/SuppressionParserTest.java index d911c9cb2..d09ac9ee9 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionParserTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/SuppressionParserTest.java @@ -15,7 +15,7 @@ * * Copyright (c) 2013 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; import java.io.File; import java.util.List; diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionRuleTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/SuppressionRuleTest.java similarity index 99% rename from dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionRuleTest.java rename to dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/SuppressionRuleTest.java index 0a73d13a1..56982887a 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/suppression/SuppressionRuleTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/suppression/SuppressionRuleTest.java @@ -15,7 +15,7 @@ * * Copyright (c) 2013 Jeremy Long. All Rights Reserved. */ -package org.owasp.dependencycheck.suppression; +package org.owasp.dependencycheck.xml.suppression; import java.io.File; import java.util.ArrayList; diff --git a/dependency-check-core/src/test/resources/hints.xml b/dependency-check-core/src/test/resources/hints.xml index 000028414..bf739a083 100644 --- a/dependency-check-core/src/test/resources/hints.xml +++ b/dependency-check-core/src/test/resources/hints.xml @@ -1,5 +1,5 @@ - + diff --git a/dependency-check-core/src/test/resources/swift/Gloss/Gloss.podspec b/dependency-check-core/src/test/resources/swift/Gloss/Gloss.podspec new file mode 100644 index 000000000..3d05500ca --- /dev/null +++ b/dependency-check-core/src/test/resources/swift/Gloss/Gloss.podspec @@ -0,0 +1,17 @@ +Pod::Spec.new do |s| + s.name = "Gloss" + s.version = "0.7.2" + s.summary = "A shiny JSON parsing library in Swift" + s.description = "A shiny JSON parsing library in Swift. Features include mapping JSON to objects, mapping objects to JSON, handling of nested objects and custom transformations." + s.homepage = "https://github.com/hkellaway/Gloss" + s.license = { :type => "MIT", :file => "LICENSE" } + s.author = { "Harlan Kellaway" => "hello@harlankellaway.com" } + s.social_media_url = "http://twitter.com/HarlanKellaway" + s.source = { :git => "https://github.com/hkellaway/Gloss.git", :tag => s.version.to_s } + + s.platforms = { :ios => "8.0", :osx => "10.9", :tvos => "9.0", :watchos => "2.0" } + s.requires_arc = true + + s.source_files = 'Sources/*.{swift}' + +end diff --git a/dependency-check-core/src/test/resources/swift/Gloss/Package.swift b/dependency-check-core/src/test/resources/swift/Gloss/Package.swift new file mode 100644 index 000000000..ac1039468 --- /dev/null +++ b/dependency-check-core/src/test/resources/swift/Gloss/Package.swift @@ -0,0 +1,30 @@ +// +// Package.swift +// Gloss +// +// Copyright (c) 2015 Harlan Kellaway +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import PackageDescription + +let package = Package( + name: "Gloss" +) diff --git a/dependency-check-core/src/test/resources/swift/cocoapods/EasyPeasy.podspec b/dependency-check-core/src/test/resources/swift/cocoapods/EasyPeasy.podspec new file mode 100644 index 000000000..52d0ef7a8 --- /dev/null +++ b/dependency-check-core/src/test/resources/swift/cocoapods/EasyPeasy.podspec @@ -0,0 +1,25 @@ +Pod::Spec.new do |s| + s.name = "EasyPeasy" + s.version = "0.2.3" + s.summary = "EasyPeasy is a Swift framework that eases the creation of + Autolayout constraints programmatically" + s.description = <<-DESC + EasyPeasy is a Swift framework that lets you create Autolayout constraints + programmatically without headaches and never ending boilerplate code. Besides the + basics, **EasyPeasy** resolves most of the constraint conflicts for you and lets + you attach to a constraint conditional closures that are evaluated before applying + a constraint, this lets you apply (or not) a constraint depending on platform, size + classes, orientation... or the state of your controller, easy peasy! + DESC + s.homepage = "https://github.com/nakiostudio/EasyPeasy" + s.license = 'MIT' + s.author = { "Carlos Vidal" => "nakioparkour@gmail.com" } + s.source = { :git => "https://github.com/nakiostudio/EasyPeasy.git", :tag => s.version.to_s } + s.social_media_url = 'https://twitter.com/carlostify' + + s.platform = :ios, '8.0' + s.requires_arc = true + + s.source_files = 'EasyPeasy/**/*' + s.frameworks = 'UIKit' +end diff --git a/dependency-check-maven/pom.xml b/dependency-check-maven/pom.xml index aa33ce2f8..c1fe6aa74 100644 --- a/dependency-check-maven/pom.xml +++ b/dependency-check-maven/pom.xml @@ -20,7 +20,7 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. org.owasp dependency-check-parent - 1.4.1-SNAPSHOT + 1.4.4-SNAPSHOT dependency-check-maven @@ -208,6 +208,10 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. org.sonatype.plexus plexus-sec-dispatcher + + org.apache.maven.shared + maven-dependency-tree + org.jmockit jmockit diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java index f801f000d..75ae844bb 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java @@ -19,10 +19,8 @@ package org.owasp.dependencycheck.maven; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Locale; import java.util.Set; import org.apache.maven.plugin.MojoExecutionException; @@ -32,22 +30,21 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; -import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer; -import org.owasp.dependencycheck.analyzer.exception.AnalysisException; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; -import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.exception.ExceptionCollection; +import org.owasp.dependencycheck.exception.ReportException; import org.owasp.dependencycheck.utils.Settings; /** - * Maven Plugin that checks project dependencies and the dependencies of all child modules to see if they have any known published - * vulnerabilities. + * Maven Plugin that checks project dependencies and the dependencies of all + * child modules to see if they have any known published vulnerabilities. * * @author Jeremy Long */ @Mojo( name = "aggregate", defaultPhase = LifecyclePhase.VERIFY, - /*aggregator = true,*/ + aggregator = true, threadSafe = false, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresOnline = true @@ -55,104 +52,91 @@ import org.owasp.dependencycheck.utils.Settings; public class AggregateMojo extends BaseDependencyCheckMojo { /** - * Executes the aggregate dependency-check goal. This runs dependency-check and generates the subsequent reports. + * Executes the aggregate dependency-check goal. This runs dependency-check + * and generates the subsequent reports. * - * @throws MojoExecutionException thrown if there is ane exception running the mojo - * @throws MojoFailureException thrown if dependency-check is configured to fail the build + * @throws MojoExecutionException thrown if there is ane exception running + * the mojo + * @throws MojoFailureException thrown if dependency-check is configured to + * fail the build */ @Override public void runCheck() throws MojoExecutionException, MojoFailureException { - final Engine engine = generateDataFile(); + final MavenEngine engine = loadEngine(); + if (engine == null) { + return; + } - //if (getProject() == getReactorProjects().get(getReactorProjects().size() - 1)) { - if (getProject() == getLastProject()) { + ExceptionCollection exCol = scanArtifacts(getProject(), engine); - //ensure that the .ser file was created for each. - for (MavenProject current : getReactorProjects()) { - final File dataFile = getDataFile(current); - if (dataFile == null && !skipProject(current)) { //dc was never run on this project. write the ser to the target. - getLog().error(String.format("Module '%s' did not execute dependency-check; an attempt will be made to perform " - + "the check but dependencies may be missed resulting in false negatives.", current.getName())); - generateDataFile(engine, current); + for (MavenProject childProject : getDescendants(this.getProject())) { + final ExceptionCollection ex = scanArtifacts(childProject, engine); + if (ex != null) { + if (exCol == null) { + exCol = ex; } - } - - for (MavenProject current : getReactorProjects()) { - List dependencies = readDataFile(current); - if (dependencies == null) { - dependencies = new ArrayList(); + exCol.getExceptions().addAll(ex.getExceptions()); + if (ex.isFatal()) { + exCol.setFatal(true); } - final Set childProjects = getDescendants(current); - for (MavenProject reportOn : childProjects) { - final List childDeps = readDataFile(reportOn); - if (childDeps != null && !childDeps.isEmpty()) { - if (getLog().isDebugEnabled()) { - getLog().debug(String.format("Adding %d dependencies from %s", childDeps.size(), reportOn.getName())); - } - dependencies.addAll(childDeps); - } else { - if (getLog().isDebugEnabled()) { - getLog().debug(String.format("No dependencies read for %s", reportOn.getName())); - } - } - } - engine.getDependencies().clear(); - engine.getDependencies().addAll(dependencies); - final DependencyBundlingAnalyzer bundler = new DependencyBundlingAnalyzer(); - try { - if (getLog().isDebugEnabled()) { - getLog().debug(String.format("Dependency count pre-bundler: %s", engine.getDependencies().size())); - } - bundler.analyze(null, engine); - if (getLog().isDebugEnabled()) { - getLog().debug(String.format("Dependency count post-bundler: %s", engine.getDependencies().size())); - } - } catch (AnalysisException ex) { - getLog().warn("An error occurred grouping the dependencies; duplicate entries may exist in the report", ex); - getLog().debug("Bundling Exception", ex); - } - - File outputDir = getCorrectOutputDirectory(current); - if (outputDir == null) { - //in some regards we shouldn't be writting this, but we are anyway. - //we shouldn't write this because nothing is configured to generate this report. - outputDir = new File(current.getBuild().getDirectory()); - } - writeReports(engine, current, outputDir); } } + + try { + engine.analyzeDependencies(); + } catch (ExceptionCollection ex) { + if (exCol == null) { + exCol = ex; + } else if (ex.isFatal()) { + exCol.setFatal(true); + exCol.getExceptions().addAll(ex.getExceptions()); + } + if (exCol.isFatal()) { + final String msg = String.format("Fatal exception(s) analyzing %s", getProject().getName()); + if (this.isFailOnError()) { + throw new MojoExecutionException(msg, exCol); + } + getLog().error(msg); + if (getLog().isDebugEnabled()) { + getLog().debug(exCol); + } + return; + } else { + final String msg = String.format("Exception(s) analyzing %s", getProject().getName()); + if (getLog().isDebugEnabled()) { + getLog().debug(msg, exCol); + } + } + } + File outputDir = getCorrectOutputDirectory(this.getProject()); + if (outputDir == null) { + //in some regards we shouldn't be writting this, but we are anyway. + //we shouldn't write this because nothing is configured to generate this report. + outputDir = new File(this.getProject().getBuild().getDirectory()); + } + try { + writeReports(engine, this.getProject(), outputDir); + } catch (ReportException ex) { + if (exCol == null) { + exCol = new ExceptionCollection("Error writing aggregate report", ex); + } else { + exCol.addException(ex); + } + if (this.isFailOnError()) { + throw new MojoExecutionException("One or more exceptions occured during dependency-check analysis", exCol); + } else { + getLog().debug("One or more exceptions occured during dependency-check analysis", exCol); + } + } + showSummary(this.getProject(), engine.getDependencies()); + checkForFailure(engine.getDependencies()); engine.cleanup(); Settings.cleanup(); } /** - * Gets the last project in the reactor - taking into account skipped projects. - * - * @return the last project in the reactor - */ - private MavenProject getLastProject() { - for (int x = getReactorProjects().size() - 1; x >= 0; x--) { - final MavenProject p = getReactorProjects().get(x); - if (!skipProject(p)) { - return p; - } - } - return null; - } - - /** - * Tests if the project is being skipped in the Maven site report. - * - * @param project a project in the reactor - * @return true if the project is skipped; otherwise false - */ - private boolean skipProject(MavenProject project) { - final String skip = (String) project.getProperties().get("maven.site.skip"); - return "true".equalsIgnoreCase(skip) && isGeneratingSite(); - } - - /** - * Returns a set containing all the descendant projects of the given project. + * Returns a set containing all the descendant projects of the given + * project. * * @param project the project for which all descendants will be returned * @return the set of descendant projects @@ -232,53 +216,36 @@ public class AggregateMojo extends BaseDependencyCheckMojo { * Test if the project has pom packaging * * @param mavenProject Project to test - * @return true if it has a pom packaging; otherwise false + * @return true if it has a pom packaging; otherwise + * false */ protected boolean isMultiModule(MavenProject mavenProject) { return "pom".equals(mavenProject.getPackaging()); } /** - * Initializes the engine, runs a scan, and writes the serialized dependencies to disk. + * Initializes the engine. * - * @return the Engine used to execute dependency-check - * @throws MojoExecutionException thrown if there is an exception running the mojo - * @throws MojoFailureException thrown if dependency-check is configured to fail the build if severe CVEs are identified. + * @return the MavenEngine used to execute dependency-check + * @throws MojoExecutionException thrown if there is an exception running + * the Mojo + * @throws MojoFailureException thrown if dependency-check is configured to + * fail the build if severe CVEs are identified. */ - protected Engine generateDataFile() throws MojoExecutionException, MojoFailureException { - final Engine engine; + protected MavenEngine loadEngine() throws MojoExecutionException, MojoFailureException { + MavenEngine engine = null; try { engine = initializeEngine(); } catch (DatabaseException ex) { if (getLog().isDebugEnabled()) { getLog().debug("Database connection error", ex); } - throw new MojoExecutionException("An exception occured connecting to the local database. Please see the log file for more details.", ex); + final String msg = "An exception occured connecting to the local database. Please see the log file for more details."; + if (this.isFailOnError()) { + throw new MojoExecutionException(msg, ex); + } + getLog().error(msg, ex); } - return generateDataFile(engine, getProject()); - } - - /** - * Runs dependency-check's Engine and writes the serialized dependencies to disk. - * - * @param engine the Engine to use when scanning. - * @param project the project to scan and generate the data file for - * @return the Engine used to execute dependency-check - * @throws MojoExecutionException thrown if there is an exception running the mojo - * @throws MojoFailureException thrown if dependency-check is configured to fail the build if severe CVEs are identified. - */ - protected Engine generateDataFile(Engine engine, MavenProject project) throws MojoExecutionException, MojoFailureException { - if (getLog().isDebugEnabled()) { - getLog().debug(String.format("Begin Scanning: %s", project.getName())); - } - engine.getDependencies().clear(); - engine.resetFileTypeAnalyzers(); - scanArtifacts(project, engine); - engine.analyzeDependencies(); - final File target = new File(project.getBuild().getDirectory()); - writeDataFile(project, target, engine.getDependencies()); - showSummary(project, engine.getDependencies()); - checkForFailure(engine.getDependencies()); return engine; } @@ -306,7 +273,8 @@ public class AggregateMojo extends BaseDependencyCheckMojo { } /** - * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page. + * Gets the description of the Dependency-Check report to be displayed in + * the Maven Generated Reports page. * * @param locale The Locale to get the description for * @return the description diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java index aff052420..e492589b9 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -27,7 +27,7 @@ import java.io.InputStream; import java.io.ObjectOutputStream; import java.util.List; import java.util.Locale; -import org.apache.maven.artifact.Artifact; +import org.eclipse.aether.artifact.Artifact; import org.apache.maven.doxia.sink.Sink; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -39,6 +39,16 @@ import org.apache.maven.reporting.MavenReport; import org.apache.maven.reporting.MavenReportException; import org.apache.maven.settings.Proxy; import org.apache.maven.settings.Server; +import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder; +import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException; +import org.apache.maven.shared.dependency.graph.DependencyNode; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; import org.owasp.dependencycheck.data.nexus.MavenArtifact; import org.owasp.dependencycheck.data.nvdcve.CveDB; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; @@ -47,6 +57,8 @@ import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.exception.ExceptionCollection; +import org.owasp.dependencycheck.exception.ReportException; import org.owasp.dependencycheck.reporting.ReportGenerator; import org.owasp.dependencycheck.utils.ExpectedOjectInputStream; import org.owasp.dependencycheck.utils.Settings; @@ -69,14 +81,28 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma * System specific new line character. */ private static final String NEW_LINE = System.getProperty("line.separator", "\n").intern(); + // + // /** * Sets whether or not the external report format should be used. */ @Parameter(property = "metaFileName", defaultValue = "dependency-check.ser", required = true) private String dataFileName; + /** + * Sets whether or not the external report format should be used. + */ + @Parameter(property = "failOnError", defaultValue = "true", required = true) + private boolean failOnError; + + /** + * Returns if the mojo should fail the build if an exception occurs. + * + * @return whether or not the mojo should fail the build + */ + protected boolean isFailOnError() { + return failOnError; + } - // - // /** * The Maven Project Object. */ @@ -87,6 +113,30 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma */ @Parameter(readonly = true, required = true, property = "reactorProjects") private List reactorProjects; + /** + * The entry point to Aether, i.e. the component doing all the work. + */ + @Component + private RepositorySystem repoSystem; + + /** + * The current repository/network configuration of Maven. + */ + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) + private RepositorySystemSession repoSession; + + /** + * The project's remote repositories to use for the resolution of plug-ins + * and their dependencies. + */ + @Parameter(defaultValue = "${project.remotePluginRepositories}", readonly = true) + private List remoteRepos; + + /** + * Component within Maven to build the dependency graph. + */ + @Component + private DependencyGraphBuilder dependencyGraphBuilder; /** * The output directory. This generally maps to "target". @@ -111,13 +161,11 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma * Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not * recommended that this be turned to false. Default is true. */ - @SuppressWarnings("CanBeFinal") @Parameter(property = "autoUpdate") private Boolean autoUpdate; /** * Sets whether Experimental analyzers are enabled. Default is false. */ - @SuppressWarnings("CanBeFinal") @Parameter(property = "enableExperimental") private Boolean enableExperimental; /** @@ -145,7 +193,6 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma /** * The maven settings proxy id. */ - @SuppressWarnings("CanBeFinal") @Parameter(property = "mavenSettingsProxyId", required = false) private String mavenSettingsProxyId; @@ -162,6 +209,7 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma /** * Flag indicating whether or not to show a summary in the output. */ + @SuppressWarnings("CanBeFinal") @Parameter(property = "showSummary", defaultValue = "true", required = false) private boolean showSummary = true; @@ -540,32 +588,121 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma * * @param project the project to scan the dependencies of * @param engine the engine to use to scan the dependencies + * @return a collection of exceptions that may have occurred while resolving + * and scanning the dependencies */ - protected void scanArtifacts(MavenProject project, Engine engine) { - for (Artifact a : project.getArtifacts()) { + protected ExceptionCollection scanArtifacts(MavenProject project, MavenEngine engine) { + // + /* + for (Artifact a : project.getArtifacts()) { if (excludeFromScan(a)) { - continue; + continue; } final List deps = engine.scan(a.getFile().getAbsoluteFile()); if (deps != null) { - if (deps.size() == 1) { - final Dependency d = deps.get(0); - if (d != null) { - final MavenArtifact ma = new MavenArtifact(a.getGroupId(), a.getArtifactId(), a.getVersion()); - d.addAsEvidence("pom", ma, Confidence.HIGHEST); - d.addProjectReference(project.getName()); - if (getLog().isDebugEnabled()) { - getLog().debug(String.format("Adding project reference %s on dependency %s", project.getName(), - d.getDisplayFileName())); + if (deps.size() == 1) { + final Dependency d = deps.get(0); + if (d != null) { + final MavenArtifact ma = new MavenArtifact(a.getGroupId(), a.getArtifactId(), a.getVersion()); + d.addAsEvidence("pom", ma, Confidence.HIGHEST); + d.addProjectReference(project.getName()); + if (getLog().isDebugEnabled()) { + getLog().debug(String.format("Adding project reference %s on dependency %s", project.getName(), + d.getDisplayFileName())); + } + } + } else if (getLog().isDebugEnabled()) { + final String msg = String.format("More then 1 dependency was identified in first pass scan of '%s:%s:%s'", + a.getGroupId(), a.getArtifactId(), a.getVersion()); + getLog().debug(msg); + } + } + } + */ + // + try { + final DependencyNode dn = dependencyGraphBuilder.buildDependencyGraph(project, null, reactorProjects); + return collectDependencies(engine, project, dn.getChildren()); + } catch (DependencyGraphBuilderException ex) { + final String msg = String.format("Unable to build dependency graph on project %s", project.getName()); + getLog().debug(msg, ex); + return new ExceptionCollection(msg, ex); + } + } + + /** + * Resolves the projects artifacts using Aether and scans the resulting + * dependencies. + * + * @param engine the core dependency-check engine + * @param project the project being scanned + * @param nodes the list of dependency nodes, generally obtained via the + * DependencyGraphBuilder + * @return a collection of exceptions that may have occurred while resolving + * and scanning the dependencies + */ + private ExceptionCollection collectDependencies(MavenEngine engine, MavenProject project, List nodes) { + ExceptionCollection exCol = null; + for (DependencyNode dependencyNode : nodes) { + exCol = collectDependencies(engine, project, dependencyNode.getChildren()); + if (excludeFromScan(dependencyNode.getArtifact().getScope())) { + continue; + } + final ArtifactRequest request = new ArtifactRequest(); + request.setArtifact(new DefaultArtifact(dependencyNode.getArtifact().getId())); + request.setRepositories(remoteRepos); + try { + final ArtifactResult result = repoSystem.resolveArtifact(repoSession, request); + if (result.isResolved() && result.getArtifact() != null && result.getArtifact().getFile() != null) { + final List deps = engine.scan(result.getArtifact().getFile().getAbsoluteFile()); + if (deps != null) { + if (deps.size() == 1) { + final Dependency d = deps.get(0); + if (d != null) { + final Artifact a = result.getArtifact(); + final MavenArtifact ma = new MavenArtifact(a.getGroupId(), a.getArtifactId(), a.getVersion()); + d.addAsEvidence("pom", ma, Confidence.HIGHEST); + d.addProjectReference(project.getName() + ":" + dependencyNode.getArtifact().getScope()); + if (getLog().isDebugEnabled()) { + getLog().debug(String.format("Adding project reference %s on dependency %s", + project.getName(), d.getDisplayFileName())); + } + } + } else if (getLog().isDebugEnabled()) { + final String msg = String.format("More then 1 dependency was identified in first pass scan of '%s' in project %s", + dependencyNode.getArtifact().getId(), project.getName()); + getLog().debug(msg); + } + } else { + final String msg = String.format("Error resolving '%s' in project %s", + dependencyNode.getArtifact().getId(), project.getName()); + if (exCol == null) { + exCol = new ExceptionCollection(); + } + getLog().error(msg); + for (Exception ex : result.getExceptions()) { + exCol.addException(ex); } } - } else if (getLog().isDebugEnabled()) { - final String msg = String.format("More then 1 dependency was identified in first pass scan of '%s:%s:%s'", - a.getGroupId(), a.getArtifactId(), a.getVersion()); + } else { + final String msg = String.format("Unable to resolve '%s' in project %s", + dependencyNode.getArtifact().getId(), project.getName()); getLog().debug(msg); + if (exCol == null) { + exCol = new ExceptionCollection(); + } + for (Exception ex : result.getExceptions()) { + exCol.addException(ex); + } } + } catch (ArtifactResolutionException ex) { + if (exCol == null) { + exCol = new ExceptionCollection(); + } + exCol.addException(ex); } } + return exCol; } /** @@ -649,15 +786,14 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma // /** - * Initializes a new Engine that can be used for scanning. + * Initializes a new MavenEngine that can be used for scanning. * - * @return a newly instantiated Engine + * @return a newly instantiated MavenEngine * @throws DatabaseException thrown if there is a database exception */ - protected Engine initializeEngine() throws DatabaseException { + protected MavenEngine initializeEngine() throws DatabaseException { populateSettings(); - return new Engine(this.project, - this.reactorProjects); + return new MavenEngine(this.project, this.reactorProjects); } /** @@ -822,18 +958,18 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma * Tests is the artifact should be included in the scan (i.e. is the * dependency in a scope that is being scanned). * - * @param a the Artifact to test + * @param scope the scope of the artifact to test * @return true if the artifact is in an excluded scope; * otherwise false */ - protected boolean excludeFromScan(Artifact a) { - if (skipTestScope && Artifact.SCOPE_TEST.equals(a.getScope())) { + protected boolean excludeFromScan(String scope) { + if (skipTestScope && org.apache.maven.artifact.Artifact.SCOPE_TEST.equals(scope)) { return true; } - if (skipProvidedScope && Artifact.SCOPE_PROVIDED.equals(a.getScope())) { + if (skipProvidedScope && org.apache.maven.artifact.Artifact.SCOPE_PROVIDED.equals(scope)) { return true; } - if (skipRuntimeScope && !Artifact.SCOPE_RUNTIME.equals(a.getScope())) { + if (skipRuntimeScope && !org.apache.maven.artifact.Artifact.SCOPE_RUNTIME.equals(scope)) { return true; } return false; @@ -875,10 +1011,11 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma * Generates the reports for a given dependency-check engine. * * @param engine a dependency-check engine - * @param p the maven project - * @param outputDir the directory path to write the report(s). + * @param p the Maven project + * @param outputDir the directory path to write the report(s) + * @throws ReportException thrown if there is an error writing the report */ - protected void writeReports(Engine engine, MavenProject p, File outputDir) { + protected void writeReports(MavenEngine engine, MavenProject p, File outputDir) throws ReportException { DatabaseProperties prop = null; CveDB cve = null; try { @@ -897,19 +1034,11 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma final ReportGenerator r = new ReportGenerator(p.getName(), engine.getDependencies(), engine.getAnalyzers(), prop); try { r.generateReports(outputDir.getAbsolutePath(), format); - } catch (IOException ex) { - getLog().error( - "Unexpected exception occurred during analysis; please see the verbose error log for more details."); - if (getLog().isDebugEnabled()) { - getLog().debug("", ex); - } - } catch (Throwable ex) { - getLog().error( - "Unexpected exception occurred during analysis; please see the verbose error log for more details."); - if (getLog().isDebugEnabled()) { - getLog().debug("", ex); - } + } catch (ReportException ex) { + final String msg = String.format("Error generating the report for %s", p.getName()); + throw new ReportException(msg, ex); } + } // @@ -1074,8 +1203,8 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma * scan data between the "check" and "aggregate" phase. * * @param project the Maven project to read the data file from - * @return a Engine object populated with dependencies if the - * serialized data file exists; otherwise null is returned + * @return a MavenEngine object populated with dependencies if + * the serialized data file exists; otherwise null is returned */ protected List readDataFile(MavenProject project) { final Object oPath = project.getContextValue(this.getDataFileContextKey()); @@ -1127,4 +1256,5 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma return ret; } // + } diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java index 17ba52bf1..4017a5d93 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java @@ -26,10 +26,13 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.exception.ExceptionCollection; +import org.owasp.dependencycheck.exception.ReportException; import org.owasp.dependencycheck.utils.Settings; /** - * Maven Plugin that checks the project dependencies to see if they have any known published vulnerabilities. + * Maven Plugin that checks the project dependencies to see if they have any + * known published vulnerabilities. * * @author Jeremy Long */ @@ -45,13 +48,14 @@ public class CheckMojo extends BaseDependencyCheckMojo { /** * Returns whether or not a the report can be generated. * - * @return true if the report can be generated; otherwise false + * @return true if the report can be generated; otherwise + * false */ @Override public boolean canGenerateReport() { boolean isCapable = false; for (Artifact a : getProject().getArtifacts()) { - if (!excludeFromScan(a)) { + if (!excludeFromScan(a.getScope())) { isCapable = true; break; } @@ -60,33 +64,64 @@ public class CheckMojo extends BaseDependencyCheckMojo { } /** - * Executes the dependency-check engine on the project's dependencies and generates the report. + * Executes the dependency-check engine on the project's dependencies and + * generates the report. * - * @throws MojoExecutionException thrown if there is an exception executing the goal - * @throws MojoFailureException thrown if dependency-check is configured to fail the build + * @throws MojoExecutionException thrown if there is an exception executing + * the goal + * @throws MojoFailureException thrown if dependency-check is configured to + * fail the build */ @Override public void runCheck() throws MojoExecutionException, MojoFailureException { - final Engine engine; + MavenEngine engine = null; try { engine = initializeEngine(); } catch (DatabaseException ex) { if (getLog().isDebugEnabled()) { getLog().debug("Database connection error", ex); } - throw new MojoExecutionException("An exception occured connecting to the local database. Please see the log file for more details.", ex); + final String msg = "An exception occured connecting to the local database. Please see the log file for more details."; + if (this.isFailOnError()) { + throw new MojoExecutionException(msg, ex); + } + getLog().error(msg); } - scanArtifacts(getProject(), engine); - if (engine.getDependencies().isEmpty()) { - getLog().info("No dependencies were identified that could be analyzed by dependency-check"); - } else { - engine.analyzeDependencies(); - writeReports(engine, getProject(), getCorrectOutputDirectory()); - writeDataFile(getProject(), null, engine.getDependencies()); - showSummary(getProject(), engine.getDependencies()); - checkForFailure(engine.getDependencies()); + if (engine != null) { + ExceptionCollection exCol = scanArtifacts(getProject(), engine); + if (engine.getDependencies().isEmpty()) { + getLog().info("No dependencies were identified that could be analyzed by dependency-check"); + } else { + try { + engine.analyzeDependencies(); + } catch (ExceptionCollection ex) { + if (this.isFailOnError() && ex.isFatal()) { + throw new MojoExecutionException("One or more exceptions occured during analysis", ex); + } + exCol = ex; + } + if (exCol == null || !exCol.isFatal()) { + try { + writeReports(engine, getProject(), getCorrectOutputDirectory()); + } catch (ReportException ex) { + if (this.isFailOnError()) { + if (exCol != null) { + exCol.addException(ex); + } else { + exCol = new ExceptionCollection("Unable to write the dependency-check report", ex); + } + } + } + writeDataFile(getProject(), null, engine.getDependencies()); + showSummary(getProject(), engine.getDependencies()); + checkForFailure(engine.getDependencies()); + if (exCol != null && this.isFailOnError()) { + throw new MojoExecutionException("One or more exceptions occured during dependency-check analysis", exCol); + } + } + } + engine.cleanup(); } - engine.cleanup(); Settings.cleanup(); } @@ -109,7 +144,8 @@ public class CheckMojo extends BaseDependencyCheckMojo { } /** - * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page. + * Gets the description of the Dependency-Check report to be displayed in + * the Maven Generated Reports page. * * @param locale The Locale to get the description for * @return the description diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/Engine.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/MavenEngine.java similarity index 81% rename from dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/Engine.java rename to dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/MavenEngine.java index f849c8a7e..9edf53ae4 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/Engine.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/MavenEngine.java @@ -23,22 +23,25 @@ import org.owasp.dependencycheck.analyzer.Analyzer; import org.owasp.dependencycheck.analyzer.CPEAnalyzer; import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.data.update.exception.UpdateException; +import org.owasp.dependencycheck.exception.ExceptionCollection; +import org.owasp.dependencycheck.exception.InitializationException; import org.owasp.dependencycheck.utils.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * A modified version of the core engine specifically designed to persist some data between multiple executions of a multi-module - * Maven project. + * A modified version of the core engine specifically designed to persist some + * data between multiple executions of a multi-module Maven project. * * @author Jeremy Long */ -public class Engine extends org.owasp.dependencycheck.Engine { +public class MavenEngine extends org.owasp.dependencycheck.Engine { /** * The logger. */ - private static final transient Logger LOGGER = LoggerFactory.getLogger(Engine.class); + private static final transient Logger LOGGER = LoggerFactory.getLogger(MavenEngine.class); /** * A key used to persist an object in the MavenProject. */ @@ -52,18 +55,21 @@ public class Engine extends org.owasp.dependencycheck.Engine { */ private List reactorProjects; /** - * Key used in the MavenProject context values to note whether or not an update has been executed. + * Key used in the MavenProject context values to note whether or not an + * update has been executed. */ public static final String UPDATE_EXECUTED_FLAG = "dependency-check-update-executed"; /** - * Creates a new Engine to perform anyalsis on dependencies. + * Creates a new Engine to perform analysis on dependencies. * * @param project the current Maven project - * @param reactorProjects the reactor projects for the current Maven execution - * @throws DatabaseException thrown if there is an issue connecting to the database + * @param reactorProjects the reactor projects for the current Maven + * execution + * @throws DatabaseException thrown if there is an issue connecting to the + * database */ - public Engine(MavenProject project, List reactorProjects) throws DatabaseException { + public MavenEngine(MavenProject project, List reactorProjects) throws DatabaseException { this.currentProject = project; this.reactorProjects = reactorProjects; initializeEngine(); @@ -71,9 +77,12 @@ public class Engine extends org.owasp.dependencycheck.Engine { /** * Runs the analyzers against all of the dependencies. + * + * @throws ExceptionCollection thrown if an exception occurred; contains a + * collection of exceptions that occurred during analysis. */ @Override - public void analyzeDependencies() { + public void analyzeDependencies() throws ExceptionCollection { final MavenProject root = getExecutionRoot(); if (root != null) { LOGGER.debug("Checking root project, {}, if updates have already been completed", root.getArtifactId()); @@ -91,8 +100,10 @@ public class Engine extends org.owasp.dependencycheck.Engine { /** * Runs the update steps of dependency-check. + * + * @throws UpdateException thrown if there is an exception */ - public void update() { + public void update() throws UpdateException { final MavenProject root = getExecutionRoot(); if (root != null && root.getContextValue(UPDATE_EXECUTED_FLAG) != null) { System.setProperty(Settings.KEYS.AUTO_UPDATE, Boolean.FALSE.toString()); @@ -103,20 +114,21 @@ public class Engine extends org.owasp.dependencycheck.Engine { /** * This constructor should not be called. Use Engine(MavenProject) instead. * - * @throws DatabaseException thrown if there is an issue connecting to the database + * @throws DatabaseException thrown if there is an issue connecting to the + * database */ - private Engine() throws DatabaseException { + private MavenEngine() throws DatabaseException { } /** - * Initializes the given analyzer. This skips the initialization of the CPEAnalyzer if it has been initialized by a previous - * execution. + * Initializes the given analyzer. This skips the initialization of the + * CPEAnalyzer if it has been initialized by a previous execution. * * @param analyzer the analyzer to initialize * @return the initialized analyzer */ @Override - protected Analyzer initializeAnalyzer(Analyzer analyzer) { + protected Analyzer initializeAnalyzer(Analyzer analyzer) throws InitializationException { if (analyzer instanceof CPEAnalyzer) { CPEAnalyzer cpe = getPreviouslyLoadedCPEAnalyzer(); if (cpe != null && cpe.isOpen()) { @@ -129,7 +141,8 @@ public class Engine extends org.owasp.dependencycheck.Engine { } /** - * Releases resources used by the analyzers by calling close() on each analyzer. + * Releases resources used by the analyzers by calling close() on each + * analyzer. */ @Override public void cleanup() { @@ -195,7 +208,7 @@ public class Engine extends org.owasp.dependencycheck.Engine { * * @return the root Maven Project */ - private MavenProject getExecutionRoot() { + MavenProject getExecutionRoot() { if (reactorProjects == null) { return null; } @@ -216,8 +229,10 @@ public class Engine extends org.owasp.dependencycheck.Engine { } /** - * Resets the file type analyzers so that they can be re-used to scan additional directories. Without the reset the analyzer - * might be disabled because the first scan/analyze did not identify any files that could be processed by the analyzer. + * Resets the file type analyzers so that they can be re-used to scan + * additional directories. Without the reset the analyzer might be disabled + * because the first scan/analyze did not identify any files that could be + * processed by the analyzer. */ public void resetFileTypeAnalyzers() { for (FileTypeAnalyzer a : getFileTypeAnalyzers()) { diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java index 2075a2c0c..62c14a0dd 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java @@ -54,14 +54,20 @@ public class PurgeMojo extends BaseDependencyCheckMojo { /** * Purges the local copy of the NVD. * - * @throws MojoExecutionException thrown if there is an exception executing the goal - * @throws MojoFailureException thrown if dependency-check is configured to fail the build + * @throws MojoExecutionException thrown if there is an exception executing + * the goal + * @throws MojoFailureException thrown if dependency-check is configured to + * fail the build */ @Override public void runCheck() throws MojoExecutionException, MojoFailureException { if (getConnectionString() != null && !getConnectionString().isEmpty()) { - getLog().error("Unable to purge the local NVD when using a non-default connection string"); + final String msg = "Unable to purge the local NVD when using a non-default connection string"; + if (this.isFailOnError()) { + throw new MojoFailureException(msg); + } + getLog().error(msg); } else { populateSettings(); File db; @@ -71,13 +77,25 @@ public class PurgeMojo extends BaseDependencyCheckMojo { if (db.delete()) { getLog().info("Database file purged; local copy of the NVD has been removed"); } else { - getLog().error(String.format("Unable to delete '%s'; please delete the file manually", db.getAbsolutePath())); + final String msg = String.format("Unable to delete '%s'; please delete the file manually", db.getAbsolutePath()); + if (this.isFailOnError()) { + throw new MojoFailureException(msg); + } + getLog().error(msg); } } else { - getLog().error(String.format("Unable to purge database; the database file does not exists: %s", db.getAbsolutePath())); + final String msg = String.format("Unable to purge database; the database file does not exists: %s", db.getAbsolutePath()); + if (this.isFailOnError()) { + throw new MojoFailureException(msg); + } + getLog().error(msg); } } catch (IOException ex) { - getLog().error("Unable to delete the database"); + final String msg = "Unable to delete the database"; + if (this.isFailOnError()) { + throw new MojoExecutionException(msg, ex); + } + getLog().error(msg); } Settings.cleanup(); } @@ -95,7 +113,8 @@ public class PurgeMojo extends BaseDependencyCheckMojo { } /** - * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page. + * Gets the description of the Dependency-Check report to be displayed in + * the Maven Generated Reports page. * * @param locale The Locale to get the description for * @return the description diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java index 8bb457b9b..bedb80a7c 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java @@ -24,10 +24,12 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.ResolutionScope; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; +import org.owasp.dependencycheck.data.update.exception.UpdateException; import org.owasp.dependencycheck.utils.Settings; /** - * Maven Plugin that checks the project dependencies to see if they have any known published vulnerabilities. + * Maven Plugin that checks the project dependencies to see if they have any + * known published vulnerabilities. * * @author Jeremy Long */ @@ -51,14 +53,17 @@ public class UpdateMojo extends BaseDependencyCheckMojo { } /** - * Executes the dependency-check engine on the project's dependencies and generates the report. + * Executes the dependency-check engine on the project's dependencies and + * generates the report. * - * @throws MojoExecutionException thrown if there is an exception executing the goal - * @throws MojoFailureException thrown if dependency-check is configured to fail the build + * @throws MojoExecutionException thrown if there is an exception executing + * the goal + * @throws MojoFailureException thrown if dependency-check is configured to + * fail the build */ @Override public void runCheck() throws MojoExecutionException, MojoFailureException { - final Engine engine; + MavenEngine engine = null; try { engine = initializeEngine(); engine.update(); @@ -66,9 +71,21 @@ public class UpdateMojo extends BaseDependencyCheckMojo { if (getLog().isDebugEnabled()) { getLog().debug("Database connection error", ex); } - throw new MojoExecutionException("An exception occured connecting to the local database. Please see the log file for more details.", ex); + final String msg = "An exception occured connecting to the local database. Please see the log file for more details."; + if (this.isFailOnError()) { + throw new MojoExecutionException(msg, ex); + } + getLog().error(msg); + } catch (UpdateException ex) { + final String msg = "An exception occured while downloading updates. Please see the log file for more details."; + if (this.isFailOnError()) { + throw new MojoExecutionException(msg, ex); + } + getLog().error(msg); + } + if (engine != null) { + engine.cleanup(); } - engine.cleanup(); Settings.cleanup(); } @@ -84,7 +101,8 @@ public class UpdateMojo extends BaseDependencyCheckMojo { } /** - * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page. + * Gets the description of the Dependency-Check report to be displayed in + * the Maven Generated Reports page. * * @param locale The Locale to get the description for * @return the description @@ -93,5 +111,4 @@ public class UpdateMojo extends BaseDependencyCheckMojo { public String getDescription(Locale locale) { return "Updates the local cache of the NVD data from NIST."; } - } diff --git a/dependency-check-maven/src/site/markdown/configuration.md b/dependency-check-maven/src/site/markdown/configuration.md index 44b68a512..2707e091c 100644 --- a/dependency-check-maven/src/site/markdown/configuration.md +++ b/dependency-check-maven/src/site/markdown/configuration.md @@ -17,6 +17,7 @@ Property | Description | Default Value autoUpdate | Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. | true cveValidForHours | Sets the number of hours to wait before checking for new updates from the NVD. | 4 failBuildOnCVSS | Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 which means since the CVSS scores are 0-10, by default the build will never fail. | 11 +failOnError | Whether the build should fail if there is an error executing the dependency-check analysis | true format | The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this within the Site plugin unless the externalReport is set to true. | HTML name | The name of the report in the site | dependency-check or dependency-check:aggregate outputDirectory | The location to write the report(s). Note, this is not used if generating the report as part of a `mvn site` build | 'target' diff --git a/dependency-check-maven/src/site/markdown/index.md.vm b/dependency-check-maven/src/site/markdown/index.md.vm index 49312f4ba..c4e5c1c2a 100644 --- a/dependency-check-maven/src/site/markdown/index.md.vm +++ b/dependency-check-maven/src/site/markdown/index.md.vm @@ -53,18 +53,16 @@ Create an aggregated dependency-check report within the site. ... - - org.owasp - dependency-check-maven - ${project.version} - - - - aggregate - - - - + org.owasp + dependency-check-maven + ${project.version} + + + + aggregate + + + ... diff --git a/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java b/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java index f66e34939..4ee8a4f65 100644 --- a/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java +++ b/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java @@ -33,6 +33,7 @@ import org.apache.maven.plugin.testing.stubs.ArtifactStub; import org.apache.maven.project.MavenProject; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import org.junit.Assume; import org.junit.Test; import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.utils.InvalidSettingException; @@ -45,7 +46,8 @@ import org.owasp.dependencycheck.utils.Settings; public class BaseDependencyCheckMojoTest extends BaseTest { /** - * Checks if the test can be run. The test in this class fail, presumable due to jmockit, if the JDK is 1.8+. + * Checks if the test can be run. The test in this class fail, presumable + * due to jmockit, if the JDK is 1.8+. * * @return true if the JDK is below 1.8. */ @@ -63,7 +65,6 @@ public class BaseDependencyCheckMojoTest extends BaseTest { */ @Test public void testScanArtifacts() throws DatabaseException, InvalidSettingException { - //TODO get this to work under JDK 1.8 if (canRun()) { MavenProject project = new MockUp() { @Mock @@ -90,12 +91,16 @@ public class BaseDependencyCheckMojoTest extends BaseTest { boolean autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); - Engine engine = new Engine(null, null); + MavenEngine engine = new MavenEngine(null, null); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate); assertTrue(engine.getDependencies().isEmpty()); BaseDependencyCheckMojoImpl instance = new BaseDependencyCheckMojoImpl(); - instance.scanArtifacts(project, engine); + try { //the mock above fails under some JDKs + instance.scanArtifacts(project, engine); + } catch (NullPointerException ex) { + Assume.assumeNoException(ex); + } assertFalse(engine.getDependencies().isEmpty()); engine.cleanup(); } diff --git a/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseTest.java b/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseTest.java index 37204ce15..686e3e6b4 100644 --- a/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseTest.java +++ b/dependency-check-maven/src/test/java/org/owasp/dependencycheck/maven/BaseTest.java @@ -17,7 +17,10 @@ */ package org.owasp.dependencycheck.maven; +import java.io.IOException; import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; import org.junit.AfterClass; import org.junit.BeforeClass; import org.owasp.dependencycheck.utils.Settings; @@ -36,8 +39,20 @@ public class BaseTest { @BeforeClass public static void setUpClass() throws Exception { Settings.initialize(); - InputStream mojoProperties = BaseTest.class.getClassLoader().getResourceAsStream(BaseTest.PROPERTIES_FILE); - Settings.mergeProperties(mojoProperties); + InputStream mojoProperties = null; + try { + mojoProperties = BaseTest.class.getClassLoader().getResourceAsStream(BaseTest.PROPERTIES_FILE); + Settings.mergeProperties(mojoProperties); + } finally { + if (mojoProperties != null) { + try { + mojoProperties.close(); + } catch (IOException ex) { + Logger.getLogger(BaseTest.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } @AfterClass diff --git a/dependency-check-utils/pom.xml b/dependency-check-utils/pom.xml index 709ab9f4c..f446593d4 100644 --- a/dependency-check-utils/pom.xml +++ b/dependency-check-utils/pom.xml @@ -20,7 +20,7 @@ Copyright (c) 2014 - Jeremy Long. All Rights Reserved. org.owasp dependency-check-parent - 1.4.1-SNAPSHOT + 1.4.4-SNAPSHOT dependency-check-utils diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Checksum.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Checksum.java index 62c0bf4ad..28842818d 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Checksum.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Checksum.java @@ -58,11 +58,11 @@ public final class Checksum { * @throws NoSuchAlgorithmException when an algorithm is specified that does not exist */ public static byte[] getChecksum(String algorithm, File file) throws NoSuchAlgorithmException, IOException { - MessageDigest digest = MessageDigest.getInstance(algorithm); + final MessageDigest digest = MessageDigest.getInstance(algorithm); FileInputStream fis = null; try { fis = new FileInputStream(file); - FileChannel ch = fis.getChannel(); + final FileChannel ch = fis.getChannel(); long remainingToRead = file.length(); long start = 0; while (remainingToRead > 0) { @@ -74,7 +74,7 @@ public final class Checksum { amountToRead = remainingToRead; remainingToRead = 0; } - MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, start, amountToRead); + final MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, start, amountToRead); digest.update(byteBuffer); start += amountToRead; } @@ -99,7 +99,7 @@ public final class Checksum { * @throws NoSuchAlgorithmException when the MD5 algorithm is not available */ public static String getMD5Checksum(File file) throws IOException, NoSuchAlgorithmException { - byte[] b = getChecksum("MD5", file); + final byte[] b = getChecksum("MD5", file); return getHex(b); } @@ -112,7 +112,7 @@ public final class Checksum { * @throws NoSuchAlgorithmException when the SHA1 algorithm is not available */ public static String getSHA1Checksum(File file) throws IOException, NoSuchAlgorithmException { - byte[] b = getChecksum("SHA1", file); + final byte[] b = getChecksum("SHA1", file); return getHex(b); } /** diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java index 92499975b..5965ef1a6 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Downloader.java @@ -31,10 +31,6 @@ import java.net.URL; import java.security.InvalidAlgorithmParameterException; import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; - -import static java.lang.String.format; -import static java.lang.String.format; -import static java.lang.String.format; import static java.lang.String.format; /** @@ -179,7 +175,7 @@ public final class Downloader { } LOGGER.debug("Download of {} complete", url.toString()); } catch (IOException ex) { - checkForSslExceptionn(ex); + checkForCommonExceptionTypes(ex); final String msg = format("Error saving '%s' to file '%s'%nConnection Timeout: %d%nEncoding: %s%n", url.toString(), outputPath.getAbsolutePath(), conn.getConnectTimeout(), encoding); throw new DownloadFailedException(msg, ex); @@ -265,8 +261,9 @@ public final class Downloader { } catch (URLConnectionFailureException ex) { throw new DownloadFailedException(format("Error creating URL Connection for HTTP %s request.", httpMethod), ex); } catch (IOException ex) { - checkForSslExceptionn(ex); - LOGGER.debug("IO Exception: " + ex.getMessage(), ex); + checkForCommonExceptionTypes(ex); + LOGGER.error("IO Exception: " + ex.getMessage()); + LOGGER.debug("Exception details", ex); if (ex.getCause() != null) { LOGGER.debug("IO Exception cause: " + ex.getCause().getMessage(), ex.getCause()); } @@ -296,15 +293,21 @@ public final class Downloader { /** * Analyzes the IOException, logs the appropriate information for debugging * purposes, and then throws a DownloadFailedException that wraps the IO - * Exception. + * Exception for common IO Exceptions. This is to provide additional details + * to assist in resolution of the exception. * * @param ex the original exception * @throws DownloadFailedException a wrapper exception that contains the * original exception as the cause */ - protected static void checkForSslExceptionn(IOException ex) throws DownloadFailedException { + protected static synchronized void checkForCommonExceptionTypes(IOException ex) throws DownloadFailedException { Throwable cause = ex; while (cause != null) { + if (cause instanceof java.net.UnknownHostException) { + final String msg = format("Unable to resolve domain '%s'", cause.getMessage()); + LOGGER.error(msg); + throw new DownloadFailedException(msg); + } if (cause instanceof InvalidAlgorithmParameterException) { final String keystore = System.getProperty("javax.net.ssl.keyStore"); final String version = System.getProperty("java.version"); diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java index 84fa670c0..2486e31f0 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/FileUtils.java @@ -24,6 +24,7 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.UUID; +import org.apache.commons.lang3.SystemUtils; /** * A collection of utilities for processing information about files. @@ -102,7 +103,7 @@ public final class FileUtils { * @return a String containing the bit bucket */ public static String getBitBucket() { - if (System.getProperty("os.name").startsWith("Windows")) { + if (SystemUtils.IS_OS_WINDOWS) { return BIT_BUCKET_WIN; } else { return BIT_BUCKET_UNIX; diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/SSLSocketFactoryEx.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/SSLSocketFactoryEx.java new file mode 100644 index 000000000..64ed3ae4e --- /dev/null +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/SSLSocketFactoryEx.java @@ -0,0 +1,289 @@ +package org.owasp.dependencycheck.utils; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is used to enable additional ciphers used by the SSL Socket. This + * is specifically because the NVD stopped supporting TLS 1.0 and Java 6 and 7 + * clients by default were unable to connect to download the NVD data feeds. + * + * The following code was copied from + * http://stackoverflow.com/questions/1037590/which-cipher-suites-to-enable-for-ssl-socket/23365536#23365536 + * + * @author jww + */ +public class SSLSocketFactoryEx extends SSLSocketFactory { + + /** + * The Logger for use throughout the class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(SSLSocketFactoryEx.class); + + /** + * Constructs a new SSLSocketFactory. + * + * @throws NoSuchAlgorithmException thrown when an algorithm is not + * supported + * @throws KeyManagementException thrown if initialization fails + */ + public SSLSocketFactoryEx() throws NoSuchAlgorithmException, KeyManagementException { + initSSLSocketFactoryEx(null, null, null); + } + + /** + * Constructs a new SSLSocketFactory. + * + * @param km the key manager + * @param tm the trust manager + * @param random secure random + * @throws NoSuchAlgorithmException thrown when an algorithm is not + * supported + * @throws KeyManagementException thrown if initialization fails + */ + public SSLSocketFactoryEx(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws NoSuchAlgorithmException, KeyManagementException { + initSSLSocketFactoryEx(km, tm, random); + } + + /** + * Constructs a new SSLSocketFactory. + * + * @param ctx the SSL context + * @throws NoSuchAlgorithmException thrown when an algorithm is not + * supported + * @throws KeyManagementException thrown if initialization fails + */ + public SSLSocketFactoryEx(SSLContext ctx) throws NoSuchAlgorithmException, KeyManagementException { + initSSLSocketFactoryEx(ctx); + } + + /** + * Returns the default cipher suites. + * + * @return the default cipher suites + */ + @Override + public String[] getDefaultCipherSuites() { + return sslCtxt.getSocketFactory().getDefaultCipherSuites(); + } + + /** + * Returns the supported cipher suites. + * + * @return the supported cipher suites + */ + @Override + public String[] getSupportedCipherSuites() { + return sslCtxt.getSocketFactory().getSupportedCipherSuites(); + } + + /** + * Returns the default protocols. + * + * @return the default protocols + */ + public String[] getDefaultProtocols() { + return Arrays.copyOf(protocols, protocols.length); + } + + /** + * Returns the supported protocols. + * + * @return the supported protocols + */ + public String[] getSupportedProtocols() { + return Arrays.copyOf(protocols, protocols.length); + } + + /** + * Creates an SSL Socket. + * + * @param s the base socket + * @param host the host + * @param port the port + * @param autoClose if the socket should auto-close + * @return the SSL Socket + * @throws IOException thrown if the creation fails + */ + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + final SSLSocketFactory factory = sslCtxt.getSocketFactory(); + final SSLSocket ss = (SSLSocket) factory.createSocket(s, host, port, autoClose); + + ss.setEnabledProtocols(protocols); + + return ss; + } + + /** + * Creates a new SSL Socket. + * + * @param address the address to connect to + * @param port the port number + * @param localAddress the local address + * @param localPort the local port + * @return the SSL Socket + * @throws IOException thrown if the creation fails + */ + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + final SSLSocketFactory factory = sslCtxt.getSocketFactory(); + final SSLSocket ss = (SSLSocket) factory.createSocket(address, port, localAddress, localPort); + + ss.setEnabledProtocols(protocols); + + return ss; + } + + /** + * Creates a new SSL Socket. + * + * @param host the host to connect to + * @param port the port to connect to + * @param localHost the local host + * @param localPort the local port + * @return the SSL Socket + * @throws IOException thrown if the creation fails + */ + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { + final SSLSocketFactory factory = sslCtxt.getSocketFactory(); + final SSLSocket ss = (SSLSocket) factory.createSocket(host, port, localHost, localPort); + + ss.setEnabledProtocols(protocols); + + return ss; + } + + /** + * Creates a new SSL Socket. + * + * @param host the host to connect to + * @param port the port to connect to + * @return the SSL Socket + * @throws IOException thrown if the creation fails + */ + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + final SSLSocketFactory factory = sslCtxt.getSocketFactory(); + final SSLSocket ss = (SSLSocket) factory.createSocket(host, port); + + ss.setEnabledProtocols(protocols); + + return ss; + } + + /** + * Creates a new SSL Socket. + * + * @param host the host to connect to + * @param port the port to connect to + * @return the SSL Socket + * @throws IOException thrown if the creation fails + */ + @Override + public Socket createSocket(String host, int port) throws IOException { + final SSLSocketFactory factory = sslCtxt.getSocketFactory(); + final SSLSocket ss = (SSLSocket) factory.createSocket(host, port); + + ss.setEnabledProtocols(protocols); + + return ss; + } + + /** + * Initializes the SSL Socket Factory Extension. + * + * @param km the key managers + * @param tm the trust managers + * @param random the secure random number generator + * @throws NoSuchAlgorithmException thrown when an algorithm is not + * supported + * @throws KeyManagementException thrown if initialization fails + */ + private void initSSLSocketFactoryEx(KeyManager[] km, TrustManager[] tm, SecureRandom random) + throws NoSuchAlgorithmException, KeyManagementException { + sslCtxt = SSLContext.getInstance("TLS"); + sslCtxt.init(km, tm, random); + + protocols = getProtocolList(); + } + + /** + * Initializes the SSL Socket Factory Extension. + * + * @param ctx the SSL context + * @throws NoSuchAlgorithmException thrown when an algorithm is not + * supported + * @throws KeyManagementException thrown if initialization fails + */ + private void initSSLSocketFactoryEx(SSLContext ctx) + throws NoSuchAlgorithmException, KeyManagementException { + sslCtxt = ctx; + protocols = getProtocolList(); + } + + /** + * Returns the protocol list. + * + * @return the protocol list + */ + protected String[] getProtocolList() { + final String[] preferredProtocols = {"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"}; + String[] availableProtocols = null; + + SSLSocket socket = null; + + try { + final SSLSocketFactory factory = sslCtxt.getSocketFactory(); + socket = (SSLSocket) factory.createSocket(); + + availableProtocols = socket.getSupportedProtocols(); + Arrays.sort(availableProtocols); + } catch (Exception ex) { + LOGGER.debug("Error getting protocol list, using TLSv1", ex); + return new String[]{"TLSv1"}; + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException ex) { + LOGGER.trace("Error closing socket", ex); + } + } + } + + final List aa = new ArrayList(); + for (String preferredProtocol : preferredProtocols) { + final int idx = Arrays.binarySearch(availableProtocols, preferredProtocol); + if (idx >= 0) { + aa.add(preferredProtocol); + } + } + + return aa.toArray(new String[0]); + } + + /** + * The SSL context. + */ + private SSLContext sslCtxt; + /** + * The protocols. + */ + private String[] protocols; +} diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java index 6f24387b0..359d24171 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java @@ -46,7 +46,8 @@ public final class Settings { public static final class KEYS { /** - * private constructor because this is a "utility" class containing constants + * private constructor because this is a "utility" class containing + * constants */ private KEYS() { //do nothing @@ -54,29 +55,34 @@ public final class Settings { /** * The key to obtain the application name. */ - public static final String APPLICATION_VAME = "application.name"; + public static final String APPLICATION_NAME = "application.name"; /** * The key to obtain the application version. */ public static final String APPLICATION_VERSION = "application.version"; /** - * The key to obtain the URL to retrieve the current release version from. + * The key to obtain the URL to retrieve the current release version + * from. */ public static final String ENGINE_VERSION_CHECK_URL = "engine.version.url"; /** - * The properties key indicating whether or not the cached data sources should be updated. + * The properties key indicating whether or not the cached data sources + * should be updated. */ public static final String AUTO_UPDATE = "autoupdate"; /** - * The database driver class name. If this is not in the properties file the embedded database is used. + * The database driver class name. If this is not in the properties file + * the embedded database is used. */ public static final String DB_DRIVER_NAME = "data.driver_name"; /** - * The database driver class name. If this is not in the properties file the embedded database is used. + * The database driver class name. If this is not in the properties file + * the embedded database is used. */ public static final String DB_DRIVER_PATH = "data.driver_path"; /** - * The database connection string. If this is not in the properties file the embedded database is used. + * The database connection string. If this is not in the properties file + * the embedded database is used. */ public static final String DB_CONNECTION_STRING = "data.connection_string"; /** @@ -101,36 +107,41 @@ public final class Settings { public static final String DB_VERSION = "data.version"; /** * The starts with filter used to exclude CVE entries from the database. - * By default this is set to 'cpe:/a:' which limits the CVEs imported to - * just those that are related to applications. If this were set to just - * 'cpe:' the OS, hardware, and application related CVEs would be imported. + * By default this is set to 'cpe:/a:' which limits the CVEs imported to + * just those that are related to applications. If this were set to just + * 'cpe:' the OS, hardware, and application related CVEs would be + * imported. */ public static final String CVE_CPE_STARTS_WITH_FILTER = "cve.cpe.startswith.filter"; /** - * The properties key for the URL to retrieve the "meta" data from about the CVE entries. + * The properties key for the URL to retrieve the "meta" data from about + * the CVE entries. */ public static final String CVE_META_URL = "cve.url.meta"; /** - * The properties key for the URL to retrieve the recently modified and added CVE entries (last 8 days) using the 2.0 - * schema. + * The properties key for the URL to retrieve the recently modified and + * added CVE entries (last 8 days) using the 2.0 schema. */ public static final String CVE_MODIFIED_20_URL = "cve.url-2.0.modified"; /** - * The properties key for the URL to retrieve the recently modified and added CVE entries (last 8 days) using the 1.2 - * schema. + * The properties key for the URL to retrieve the recently modified and + * added CVE entries (last 8 days) using the 1.2 schema. */ public static final String CVE_MODIFIED_12_URL = "cve.url-1.2.modified"; /** - * The properties key for the URL to retrieve the recently modified and added CVE entries (last 8 days). + * The properties key for the URL to retrieve the recently modified and + * added CVE entries (last 8 days). */ public static final String CVE_MODIFIED_VALID_FOR_DAYS = "cve.url.modified.validfordays"; /** - * The properties key to control the skipping of the check for CVE updates. + * The properties key to control the skipping of the check for CVE + * updates. */ public static final String CVE_CHECK_VALID_FOR_HOURS = "cve.check.validforhours"; /** - * The properties key for the telling us how many cve.url.* URLs exists. This is used in combination with CVE_BASE_URL to - * be able to retrieve the URLs for all of the files that make up the NVD CVE listing. + * The properties key for the telling us how many cve.url.* URLs exists. + * This is used in combination with CVE_BASE_URL to be able to retrieve + * the URLs for all of the files that make up the NVD CVE listing. */ public static final String CVE_START_YEAR = "cve.startyear"; /** @@ -142,7 +153,8 @@ public final class Settings { */ public static final String CVE_SCHEMA_2_0 = "cve.url-2.0.base"; /** - * The properties key that indicates how often the CPE data needs to be updated. + * The properties key that indicates how often the CPE data needs to be + * updated. */ public static final String CPE_MODIFIED_VALID_FOR_DAYS = "cpe.validfordays"; /** @@ -152,7 +164,9 @@ public final class Settings { /** * The properties key for the proxy server. * - * @deprecated use {@link org.owasp.dependencycheck.utils.Settings.KEYS#PROXY_SERVER} instead. + * @deprecated use + * {@link org.owasp.dependencycheck.utils.Settings.KEYS#PROXY_SERVER} + * instead. */ @Deprecated public static final String PROXY_URL = "proxy.server"; @@ -161,7 +175,8 @@ public final class Settings { */ public static final String PROXY_SERVER = "proxy.server"; /** - * The properties key for the proxy port - this must be an integer value. + * The properties key for the proxy port - this must be an integer + * value. */ public static final String PROXY_PORT = "proxy.port"; /** @@ -209,19 +224,23 @@ public final class Settings { */ public static final String ANALYZER_ARCHIVE_ENABLED = "analyzer.archive.enabled"; /** - * The properties key for whether the node.js package analyzer is enabled. + * The properties key for whether the node.js package analyzer is + * enabled. */ public static final String ANALYZER_NODE_PACKAGE_ENABLED = "analyzer.node.package.enabled"; /** - * The properties key for whether the composer lock file analyzer is enabled. + * The properties key for whether the composer lock file analyzer is + * enabled. */ public static final String ANALYZER_COMPOSER_LOCK_ENABLED = "analyzer.composer.lock.enabled"; /** - * The properties key for whether the Python Distribution analyzer is enabled. + * The properties key for whether the Python Distribution analyzer is + * enabled. */ public static final String ANALYZER_PYTHON_DISTRIBUTION_ENABLED = "analyzer.python.distribution.enabled"; /** - * The properties key for whether the Python Package analyzer is enabled. + * The properties key for whether the Python Package analyzer is + * enabled. */ public static final String ANALYZER_PYTHON_PACKAGE_ENABLED = "analyzer.python.package.enabled"; /** @@ -237,7 +256,8 @@ public final class Settings { */ public static final String ANALYZER_CMAKE_ENABLED = "analyzer.cmake.enabled"; /** - * The properties key for whether the Ruby Bundler Audit analyzer is enabled. + * The properties key for whether the Ruby Bundler Audit analyzer is + * enabled. */ public static final String ANALYZER_BUNDLE_AUDIT_ENABLED = "analyzer.bundle.audit.enabled"; /** @@ -268,6 +288,14 @@ public final class Settings { * The properties key for whether the OpenSSL analyzer is enabled. */ public static final String ANALYZER_OPENSSL_ENABLED = "analyzer.openssl.enabled"; + /** + * The properties key for whether the cocoapods analyzer is enabled. + */ + public static final String ANALYZER_COCOAPODS_ENABLED = "analyzer.cocoapods.enabled"; + /** + * The properties key for whether the SWIFT package manager analyzer is enabled. + */ + public static final String ANALYZER_SWIFT_PACKAGE_MANAGER_ENABLED = "analyzer.swift.package.manager.enabled"; /** * The properties key for the Central search URL. */ @@ -331,7 +359,8 @@ public final class Settings { private Properties props = null; /** - * Private constructor for the Settings class. This class loads the properties files. + * Private constructor for the Settings class. This class loads the + * properties files. * * @param propertiesFilePath the path to the base properties file to load */ @@ -357,16 +386,18 @@ public final class Settings { } /** - * Initializes the thread local settings object. Note, to use the settings object you must call this method. However, you must - * also call Settings.cleanup() to properly release resources. + * Initializes the thread local settings object. Note, to use the settings + * object you must call this method. However, you must also call + * Settings.cleanup() to properly release resources. */ public static void initialize() { LOCAL_SETTINGS.set(new Settings(PROPERTIES_FILE)); } /** - * Initializes the thread local settings object. Note, to use the settings object you must call this method. However, you must - * also call Settings.cleanup() to properly release resources. + * Initializes the thread local settings object. Note, to use the settings + * object you must call this method. However, you must also call + * Settings.cleanup() to properly release resources. * * @param propertiesFilePath the path to the base properties file to load */ @@ -385,7 +416,8 @@ public final class Settings { /** * Cleans up resources to prevent memory leaks. * - * @param deleteTemporary flag indicating whether any temporary directories generated should be removed + * @param deleteTemporary flag indicating whether any temporary directories + * generated should be removed */ public static void cleanup(boolean deleteTemporary) { if (deleteTemporary && tempDirectory != null && tempDirectory.exists()) { @@ -425,7 +457,8 @@ public final class Settings { } /** - * Logs the properties. This will not log any properties that contain 'password' in the key. + * Logs the properties. This will not log any properties that contain + * 'password' in the key. * * @param header the header to print with the log message * @param properties the properties to log @@ -541,13 +574,16 @@ public final class Settings { } /** - * Merges a new properties file into the current properties. This method allows for the loading of a user provided properties - * file.

- * Note: even if using this method - system properties will be loaded before properties loaded from files. + * Merges a new properties file into the current properties. This method + * allows for the loading of a user provided properties file.

+ * Note: even if using this method - system properties will be loaded + * before properties loaded from files. * * @param filePath the path to the properties file to merge. - * @throws FileNotFoundException is thrown when the filePath points to a non-existent file - * @throws IOException is thrown when there is an exception loading/merging the properties + * @throws FileNotFoundException is thrown when the filePath points to a + * non-existent file + * @throws IOException is thrown when there is an exception loading/merging + * the properties */ public static void mergeProperties(File filePath) throws FileNotFoundException, IOException { FileInputStream fis = null; @@ -566,13 +602,16 @@ public final class Settings { } /** - * Merges a new properties file into the current properties. This method allows for the loading of a user provided properties - * file.

- * Note: even if using this method - system properties will be loaded before properties loaded from files. + * Merges a new properties file into the current properties. This method + * allows for the loading of a user provided properties file.

+ * Note: even if using this method - system properties will be loaded before + * properties loaded from files. * * @param filePath the path to the properties file to merge. - * @throws FileNotFoundException is thrown when the filePath points to a non-existent file - * @throws IOException is thrown when there is an exception loading/merging the properties + * @throws FileNotFoundException is thrown when the filePath points to a + * non-existent file + * @throws IOException is thrown when there is an exception loading/merging + * the properties */ public static void mergeProperties(String filePath) throws FileNotFoundException, IOException { FileInputStream fis = null; @@ -591,12 +630,14 @@ public final class Settings { } /** - * Merges a new properties file into the current properties. This method allows for the loading of a user provided properties - * file.

- * Note: even if using this method - system properties will be loaded before properties loaded from files. + * Merges a new properties file into the current properties. This method + * allows for the loading of a user provided properties file.

+ * Note: even if using this method - system properties will be loaded + * before properties loaded from files. * * @param stream an Input Stream pointing at a properties file to merge - * @throws IOException is thrown when there is an exception loading/merging the properties + * @throws IOException is thrown when there is an exception loading/merging + * the properties */ public static void mergeProperties(InputStream stream) throws IOException { LOCAL_SETTINGS.get().props.load(stream); @@ -604,9 +645,10 @@ public final class Settings { } /** - * Returns a value from the properties file as a File object. If the value was specified as a system property or passed in via - * the -Dprop=value argument - this method will return the value from the system properties before the values in the contained - * configuration file. + * Returns a value from the properties file as a File object. If the value + * was specified as a system property or passed in via the -Dprop=value + * argument - this method will return the value from the system properties + * before the values in the contained configuration file. * * @param key the key to lookup within the properties file * @return the property from the properties file converted to a File object @@ -620,13 +662,15 @@ public final class Settings { } /** - * Returns a value from the properties file as a File object. If the value was specified as a system property or passed in via - * the -Dprop=value argument - this method will return the value from the system properties before the values in the contained - * configuration file. + * Returns a value from the properties file as a File object. If the value + * was specified as a system property or passed in via the -Dprop=value + * argument - this method will return the value from the system properties + * before the values in the contained configuration file. * - * This method will check the configured base directory and will use this as the base of the file path. Additionally, if the - * base directory begins with a leading "[JAR]\" sequence with the path to the folder containing the JAR file containing this - * class. + * This method will check the configured base directory and will use this as + * the base of the file path. Additionally, if the base directory begins + * with a leading "[JAR]\" sequence with the path to the folder containing + * the JAR file containing this class. * * @param key the key to lookup within the properties file * @return the property from the properties file converted to a File object @@ -649,7 +693,8 @@ public final class Settings { } /** - * Attempts to retrieve the folder containing the Jar file containing the Settings class. + * Attempts to retrieve the folder containing the Jar file containing the + * Settings class. * * @return a File object */ @@ -671,9 +716,10 @@ public final class Settings { } /** - * Returns a value from the properties file. If the value was specified as a system property or passed in via the -Dprop=value - * argument - this method will return the value from the system properties before the values in the contained configuration - * file. + * Returns a value from the properties file. If the value was specified as a + * system property or passed in via the -Dprop=value argument - this method + * will return the value from the system properties before the values in the + * contained configuration file. * * @param key the key to lookup within the properties file * @param defaultValue the default value for the requested property @@ -685,7 +731,8 @@ public final class Settings { } /** - * A reference to the temporary directory; used incase it needs to be deleted during cleanup. + * A reference to the temporary directory; used incase it needs to be + * deleted during cleanup. */ private static File tempDirectory = null; @@ -693,7 +740,8 @@ public final class Settings { * Returns the temporary directory. * * @return the temporary directory - * @throws java.io.IOException thrown if the temporary directory does not exist and cannot be created + * @throws java.io.IOException thrown if the temporary directory does not + * exist and cannot be created */ public static File getTempDirectory() throws IOException { final File tmpDir = new File(Settings.getString(Settings.KEYS.TEMP_DIRECTORY, System.getProperty("java.io.tmpdir")), "dctemp"); @@ -706,9 +754,10 @@ public final class Settings { } /** - * Returns a value from the properties file. If the value was specified as a system property or passed in via the -Dprop=value - * argument - this method will return the value from the system properties before the values in the contained configuration - * file. + * Returns a value from the properties file. If the value was specified as a + * system property or passed in via the -Dprop=value argument - this method + * will return the value from the system properties before the values in the + * contained configuration file. * * @param key the key to lookup within the properties file * @return the property from the properties file @@ -718,7 +767,8 @@ public final class Settings { } /** - * Removes a property from the local properties collection. This is mainly used in test cases. + * Removes a property from the local properties collection. This is mainly + * used in test cases. * * @param key the property key to remove */ @@ -727,13 +777,15 @@ public final class Settings { } /** - * Returns an int value from the properties file. If the value was specified as a system property or passed in via the - * -Dprop=value argument - this method will return the value from the system properties before the values in the contained - * configuration file. + * Returns an int value from the properties file. If the value was specified + * as a system property or passed in via the -Dprop=value argument - this + * method will return the value from the system properties before the values + * in the contained configuration file. * * @param key the key to lookup within the properties file * @return the property from the properties file - * @throws InvalidSettingException is thrown if there is an error retrieving the setting + * @throws InvalidSettingException is thrown if there is an error retrieving + * the setting */ public static int getInt(String key) throws InvalidSettingException { try { @@ -744,14 +796,15 @@ public final class Settings { } /** - * Returns an int value from the properties file. If the value was specified as a system property or passed in via the - * -Dprop=value argument - this method will return the value from the system properties before the values in the contained - * configuration file. + * Returns an int value from the properties file. If the value was specified + * as a system property or passed in via the -Dprop=value argument - this + * method will return the value from the system properties before the values + * in the contained configuration file. * * @param key the key to lookup within the properties file * @param defaultValue the default value to return - * @return the property from the properties file or the defaultValue if the property does not exist or cannot be converted to - * an integer + * @return the property from the properties file or the defaultValue if the + * property does not exist or cannot be converted to an integer */ public static int getInt(String key, int defaultValue) { int value; @@ -767,13 +820,15 @@ public final class Settings { } /** - * Returns a long value from the properties file. If the value was specified as a system property or passed in via the - * -Dprop=value argument - this method will return the value from the system properties before the values in the contained - * configuration file. + * Returns a long value from the properties file. If the value was specified + * as a system property or passed in via the -Dprop=value argument - this + * method will return the value from the system properties before the values + * in the contained configuration file. * * @param key the key to lookup within the properties file * @return the property from the properties file - * @throws InvalidSettingException is thrown if there is an error retrieving the setting + * @throws InvalidSettingException is thrown if there is an error retrieving + * the setting */ public static long getLong(String key) throws InvalidSettingException { try { @@ -784,38 +839,47 @@ public final class Settings { } /** - * Returns a boolean value from the properties file. If the value was specified as a system property or passed in via the - * -Dprop=value argument this method will return the value from the system properties before the values in the - * contained configuration file. + * Returns a boolean value from the properties file. If the value was + * specified as a system property or passed in via the + * -Dprop=value argument this method will return the value from + * the system properties before the values in the contained configuration + * file. * * @param key the key to lookup within the properties file * @return the property from the properties file - * @throws InvalidSettingException is thrown if there is an error retrieving the setting + * @throws InvalidSettingException is thrown if there is an error retrieving + * the setting */ public static boolean getBoolean(String key) throws InvalidSettingException { return Boolean.parseBoolean(Settings.getString(key)); } /** - * Returns a boolean value from the properties file. If the value was specified as a system property or passed in via the - * -Dprop=value argument this method will return the value from the system properties before the values in the - * contained configuration file. + * Returns a boolean value from the properties file. If the value was + * specified as a system property or passed in via the + * -Dprop=value argument this method will return the value from + * the system properties before the values in the contained configuration + * file. * * @param key the key to lookup within the properties file - * @param defaultValue the default value to return if the setting does not exist + * @param defaultValue the default value to return if the setting does not + * exist * @return the property from the properties file - * @throws InvalidSettingException is thrown if there is an error retrieving the setting + * @throws InvalidSettingException is thrown if there is an error retrieving + * the setting */ public static boolean getBoolean(String key, boolean defaultValue) throws InvalidSettingException { return Boolean.parseBoolean(Settings.getString(key, Boolean.toString(defaultValue))); } /** - * Returns a connection string from the configured properties. If the connection string contains a %s, this method will - * determine the 'data' directory and replace the %s with the path to the data directory. If the data directory does not - * exists it will be created. + * Returns a connection string from the configured properties. If the + * connection string contains a %s, this method will determine the 'data' + * directory and replace the %s with the path to the data directory. If the + * data directory does not exists it will be created. * - * @param connectionStringKey the property file key for the connection string + * @param connectionStringKey the property file key for the connection + * string * @param dbFileNameKey the settings key for the db filename * @return the connection string * @throws IOException thrown the data directory cannot be created @@ -852,8 +916,9 @@ public final class Settings { } /** - * Retrieves the directory that the JAR file exists in so that we can ensure we always use a common data directory for the - * embedded H2 database. This is public solely for some unit tests; otherwise this should be private. + * Retrieves the directory that the JAR file exists in so that we can ensure + * we always use a common data directory for the embedded H2 database. This + * is public solely for some unit tests; otherwise this should be private. * * @return the data directory to store data files * @throws IOException is thrown if an IOException occurs of course... diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/URLConnectionFactory.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/URLConnectionFactory.java index cbeb00a64..1d0f9db2f 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/URLConnectionFactory.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/URLConnectionFactory.java @@ -28,15 +28,27 @@ import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.SocketAddress; import java.net.URL; +import java.net.URLConnection; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import javax.net.ssl.HttpsURLConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * A URLConnection Factory to create new connections. This encapsulates several configuration checks to ensure that the connection - * uses the correct proxy settings. + * A URLConnection Factory to create new connections. This encapsulates several + * configuration checks to ensure that the connection uses the correct proxy + * settings. * * @author Jeremy Long */ public final class URLConnectionFactory { + /** + * The logger. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(URLConnectionFactory.class); + /** * Private constructor for this factory. */ @@ -44,8 +56,9 @@ public final class URLConnectionFactory { } /** - * Utility method to create an HttpURLConnection. If the application is configured to use a proxy this method will retrieve - * the proxy settings and use them when setting up the connection. + * Utility method to create an HttpURLConnection. If the application is + * configured to use a proxy this method will retrieve the proxy settings + * and use them when setting up the connection. * * @param url the url to connect to * @return an HttpURLConnection @@ -95,6 +108,7 @@ public final class URLConnectionFactory { } throw new URLConnectionFailureException("Error getting connection.", ex); } + configureTLS(url, conn); return conn; } @@ -140,8 +154,10 @@ public final class URLConnectionFactory { } /** - * Utility method to create an HttpURLConnection. The use of a proxy here is optional as there may be cases where a proxy is - * configured but we don't want to use it (for example, if there's an internal repository configured) + * Utility method to create an HttpURLConnection. The use of a proxy here is + * optional as there may be cases where a proxy is configured but we don't + * want to use it (for example, if there's an internal repository + * configured) * * @param url the URL to connect to * @param proxy whether to use the proxy (if configured) @@ -161,6 +177,29 @@ public final class URLConnectionFactory { } catch (IOException ioe) { throw new URLConnectionFailureException("Error getting connection.", ioe); } + configureTLS(url, conn); return conn; } + + /** + * If the protocol is HTTPS, this will configure the cipher suites so that + * connections can be made to the NVD, and others, using older versions of + * Java. + * + * @param url the URL + * @param conn the connection + */ + private static void configureTLS(URL url, URLConnection conn) { + if ("https".equals(url.getProtocol())) { + try { + final HttpsURLConnection secCon = (HttpsURLConnection) conn; + final SSLSocketFactoryEx factory = new SSLSocketFactoryEx(); + secCon.setSSLSocketFactory(factory); + } catch (NoSuchAlgorithmException ex) { + LOGGER.debug("Unsupported algorithm in SSLSocketFactoryEx", ex); + } catch (KeyManagementException ex) { + LOGGER.debug("Key mnagement eception in SSLSocketFactoryEx", ex); + } + } + } } diff --git a/pom.xml b/pom.xml index 0576d574c..c44d75cd0 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ Copyright (c) 2012 - Jeremy Long org.owasp dependency-check-parent - 1.4.1-SNAPSHOT + 1.4.4-SNAPSHOT pom @@ -360,7 +360,7 @@ Copyright (c) 2012 - Jeremy Long - + @@ -671,6 +671,11 @@ Copyright (c) 2012 - Jeremy Long plexus-sec-dispatcher 1.4
+ + org.apache.maven.shared + maven-dependency-tree + 2.2 + org.glassfish javax.json diff --git a/src/main/config/checkstyle-header.txt b/src/main/config/checkstyle-header.txt index 2e87cd304..aef133a42 100644 --- a/src/main/config/checkstyle-header.txt +++ b/src/main/config/checkstyle-header.txt @@ -13,6 +13,6 @@ ^ \* See the License for the specific language governing permissions and\s*$ ^ \* limitations under the License\.\s*$ ^ \*\s*$ -^ \* Copyright \(c\) 201[0-9] (Jeremy Long|Steve Springett|Bianca Jiang|The OWASP Foundation|Institute for Defense Analyses)\. All Rights Reserved\.\s*$ +^ \* Copyright \(c\) 201[0-9] (Jeremy Long|Steve Springett|Bianca Jiang|IBM Corporation|The OWASP Foundation|Institute for Defense Analyses)\. All Rights Reserved\.\s*$ ^ \*/\s*$ ^package diff --git a/src/main/config/checkstyle-suppressions.xml b/src/main/config/checkstyle-suppressions.xml index 27c63c4b8..56ec1750f 100644 --- a/src/main/config/checkstyle-suppressions.xml +++ b/src/main/config/checkstyle-suppressions.xml @@ -7,8 +7,9 @@ - + - + + \ No newline at end of file diff --git a/src/site/markdown/data/database.md b/src/site/markdown/data/database.md.vm similarity index 60% rename from src/site/markdown/data/database.md rename to src/site/markdown/data/database.md.vm index b41b7fb61..b5a6b514d 100644 --- a/src/site/markdown/data/database.md +++ b/src/site/markdown/data/database.md.vm @@ -7,8 +7,8 @@ file is configured using the data directory configuration option (see [CLI](https://jeremylong.github.io/DependencyCheck/dependency-check-cli/arguments.html)). Some organizations may want to use a more robust centralized database. Currently, [H2 in -server mode](http://www.h2database.com/html/tutorial.html#using_server) and -[MySQL](https://www.mysql.com/) have been tested. In general, the setup is done by creating +server mode](http://www.h2database.com/html/tutorial.html#using_server), MySQL, MariaDB, PostgreSQL, +Oracle, and MS SQL Server have been tested. In general, the setup is done by creating a central database, setting up a single instance of dependency-check, which can connect to the Internet, that is run in update-only mode once a day. Then the other dependency-check clients can connect, using a read-only connection, to perform the analysis. Please note that if the @@ -18,7 +18,7 @@ see the note about Central [here](./index.html). To setup a centralized database the following generalized steps can be used:
  1. Create the database and tables using either initialize.sql - or initialize_mysql.sql.
  2. + or one of the other initialization scripts found here.
  3. The account that the clients will connect using must have select granted on the tables.
    • Note, if the clients performing the scans should run with the noupdate setting. A single instance of the dependency-check client should be setup with update enabled and the account @@ -32,11 +32,55 @@ To setup a centralized database the following generalized steps can be used:
Depending on the database being used, you may need to customize the [dbStatements.properties](https://github.com/jeremylong/DependencyCheck/blob/master/dependency-check-core/src/main/resources/data/dbStatements.properties). -Alternatively to modifying the dbStatements.properties it is now possible to use a dialect file to support other databases. +Alternatively to modifying the dbStatements.properties it is possible to use a dialect file to support other databases. See [dbStatements_h2.properties](https://github.com/jeremylong/DependencyCheck/blob/master/dependency-check-core/src/main/resources/data/dbStatements_h2.properties) as an example. Also, if using an external database you will need to manually upgrade the schema. See [database upgrades](./upgrade.html) for more information. +Examples +-------- +The following example shows how to use the Maven plugin with MariaDB: + +```xml + + 4.0.0 + dummy + dummy + 1.0-SNAPSHOT + + + + org.owasp + dependency-check-maven + ${project.version} + + + org.mariadb.jdbc + mariadb-java-client + 1.4.6 + + + + org.mariadb.jdbc.Driver + jdbc:mariadb://my.cvedb.host/cvedb + depscan + NotReallyMyDbPassword + + + + + update-only + + + + + + + +``` + +Support +------- As always, feel free to open an [issue](https://github.com/jeremylong/DependencyCheck/issues) or post a question to the [dependency-check google group](https://groups.google.com/forum/#!forum/dependency-check). diff --git a/src/site/markdown/dependency-check-gradle/configuration.md b/src/site/markdown/dependency-check-gradle/configuration.md index 5234be304..25a0c83aa 100644 --- a/src/site/markdown/dependency-check-gradle/configuration.md +++ b/src/site/markdown/dependency-check-gradle/configuration.md @@ -20,13 +20,15 @@ format | The report format to be generated (HTML, XML, VULN, ALL). outputDirectory | The location to write the report(s). This directory will be located in the build directory. | build/reports skipTestGroups | When set to true (the default) all dependency groups that being with 'test' will be skipped. | true suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html) |   +skipConfigurations | A list of configurations that will be skipped. This is mutually exclusive with the scanConfigurations property. | `[]` which means no configuration is skipped. +scanConfigurations | A list of configurations that will be scanned, all other configurations are skipped. This is mutually exclusive with the skipConfigurations property. | `[]` which implicitly means all configurations get scanned. #### Example ```groovy dependencyCheck { autoUpdate=false cveValidForHours=1 - format=ALL + format='ALL' } ``` diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index 81273af65..81a187625 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -28,11 +28,12 @@ More information about dependency-check can be found here: OWASP dependency-check's core analysis engine can be used as: -- [Command Line Tool](dependency-check-cli/index.html) -- [Maven Plugin](dependency-check-maven/index.html) - [Ant Task](dependency-check-ant/index.html) +- [Command Line Tool](dependency-check-cli/index.html) - [Gradle Plugin](dependency-check-gradle/index.html) - [Jenkins Plugin](dependency-check-jenkins/index.html) +- [Maven Plugin](dependency-check-maven/index.html) +- [SBT Plugin](https://github.com/albuch/sbt-dependency-check) For help with dependency-check the following resource can be used: diff --git a/src/site/markdown/modules.md b/src/site/markdown/modules.md index 1a0d027fc..012988c08 100644 --- a/src/site/markdown/modules.md +++ b/src/site/markdown/modules.md @@ -3,10 +3,12 @@ Modules OWASP dependency-check's core analysis engine was designed to fit into an applications normal build and reporting process: -- [Maven Plugin](dependency-check-maven/index.html) -- [Ant Task](dependency-check-ant/index.html) -- [Gradle Plugin](dependency-check-gradle/index.html) -- [Jenkins Plugin](dependency-check-jenkins/index.html) +- [Ant Task](dependency-check-ant/index.html) +- [Command Line Tool](dependency-check-cli/index.html) +- [Gradle Plugin](dependency-check-gradle/index.html) +- [Jenkins Plugin](dependency-check-jenkins/index.html) +- [Maven Plugin](dependency-check-maven/index.html) +- [SBT Plugin](https://github.com/albuch/sbt-dependency-check) In addition, dependency-check can be executed from the [command line](dependency-check-cli/index.html).