entries = searchCPE(vendors, products, dependency.getProductEvidence().getWeighting(),
+ dependency.getVendorEvidence().getWeighting());
+
+ boolean identifierAdded = false;
+ for (IndexEntry e : entries) {
+ if (verifyEntry(e, dependency)) {
+ final String vendor = e.getVendor();
+ final String product = e.getProduct();
+ identifierAdded |= determineIdentifiers(dependency, vendor, product, confidence);
+ }
+ }
+ if (identifierAdded) {
+ break;
+ }
}
- } while ((++ctr) < 4);
+ }
}
/**
@@ -239,22 +236,6 @@ public class CPEAnalyzer implements Analyzer {
return sb.toString().trim();
}
- /**
- * Reduces the given confidence by one level. This returns LOW if the confidence passed in is not HIGH.
- *
- * @param c the confidence to reduce.
- * @return One less then the confidence passed in.
- */
- private Confidence reduceConfidence(final Confidence c) {
- if (c == Confidence.HIGHEST) {
- return Confidence.HIGH;
- } else if (c == Confidence.HIGH) {
- return Confidence.MEDIUM;
- } else {
- return Confidence.LOW;
- }
- }
-
/**
*
* Searches the Lucene CPE index to identify possible CPE entries associated with the supplied vendor, product, and
@@ -508,14 +489,19 @@ public class CPEAnalyzer implements Analyzer {
* @param dependency the Dependency being analyzed
* @param vendor the vendor for the CPE being analyzed
* @param product the product for the CPE being analyzed
+ * @return true if an identifier was added to the dependency; otherwise false
* @throws UnsupportedEncodingException is thrown if UTF-8 is not supported
*/
- private void determineIdentifiers(Dependency dependency, String vendor, String product) throws UnsupportedEncodingException {
+ private boolean determineIdentifiers(Dependency dependency, String vendor, String product, Confidence currentConfidence) throws UnsupportedEncodingException {
final Set cpes = cve.getCPEs(vendor, product);
DependencyVersion bestGuess = new DependencyVersion("-");
Confidence bestGuessConf = null;
+ boolean hasBroadMatch = false;
final List collected = new ArrayList();
for (Confidence conf : Confidence.values()) {
+// if (conf.compareTo(currentConfidence) > 0) {
+// break;
+// }
for (Evidence evidence : dependency.getVersionEvidence().iterator(conf)) {
final DependencyVersion evVer = DependencyVersionUtil.parseVersion(evidence.getValue());
if (evVer == null) {
@@ -528,9 +514,12 @@ public class CPEAnalyzer implements Analyzer {
} else {
dbVer = DependencyVersionUtil.parseVersion(vs.getVersion());
}
- if (dbVer == null //special case, no version specified - everything is vulnerable
- || evVer.equals(dbVer)) { //yeah! exact match
-
+ if (dbVer == null) { //special case, no version specified - everything is vulnerable
+ hasBroadMatch = true;
+ final String url = String.format(NVD_SEARCH_URL, URLEncoder.encode(vs.getName(), "UTF-8"));
+ final IdentifierMatch match = new IdentifierMatch("cpe", vs.getName(), url, IdentifierConfidence.BROAD_MATCH, conf);
+ collected.add(match);
+ } else if (evVer.equals(dbVer)) { //yeah! exact match
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);
@@ -556,7 +545,11 @@ public class CPEAnalyzer implements Analyzer {
}
}
final String cpeName = String.format("cpe:/a:%s:%s:%s", vendor, product, bestGuess.toString());
- final String url = null;
+ String url = null;
+ if (hasBroadMatch) { //if we have a broad match we can add the URL to the best guess.
+ final String cpeUrlName = String.format("cpe:/a:%s:%s", vendor, product);
+ url = String.format(NVD_SEARCH_URL, URLEncoder.encode(cpeUrlName, "UTF-8"));
+ }
if (bestGuessConf == null) {
bestGuessConf = Confidence.LOW;
}
@@ -566,6 +559,7 @@ public class CPEAnalyzer implements Analyzer {
Collections.sort(collected);
final IdentifierConfidence bestIdentifierQuality = collected.get(0).getConfidence();
final Confidence bestEvidenceQuality = collected.get(0).getEvidenceConfidence();
+ boolean identifierAdded = false;
for (IdentifierMatch m : collected) {
if (bestIdentifierQuality.equals(m.getConfidence())
&& bestEvidenceQuality.equals(m.getEvidenceConfidence())) {
@@ -576,8 +570,10 @@ public class CPEAnalyzer implements Analyzer {
i.setConfidence(bestEvidenceQuality);
}
dependency.addIdentifier(i);
+ identifierAdded = true;
}
}
+ return identifierAdded;
}
/**
@@ -592,7 +588,12 @@ public class CPEAnalyzer implements Analyzer {
/**
* A best guess for the CPE.
*/
- BEST_GUESS
+ BEST_GUESS,
+ /**
+ * The entire vendor/product group must be added (without a guess at version) because there is a CVE with a VS
+ * that only specifies vendor/product.
+ */
+ BROAD_MATCH
}
/**
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java
index 20f8c3f41..8f592e73a 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/FalsePositiveAnalyzer.java
@@ -86,12 +86,41 @@ public class FalsePositiveAnalyzer extends AbstractAnalyzer {
public void analyze(Dependency dependency, Engine engine) throws AnalysisException {
removeJreEntries(dependency);
removeBadMatches(dependency);
+ removeBadSpringMatches(dependency);
removeWrongVersionMatches(dependency);
removeSpuriousCPE(dependency);
removeDuplicativeEntriesFromJar(dependency, engine);
addFalseNegativeCPEs(dependency);
}
+ private void removeBadSpringMatches(Dependency dependency) {
+ String mustContain = null;
+ for (Identifier i : dependency.getIdentifiers()) {
+ if ("maven".contains(i.getType())) {
+ if (i.getValue() != null && i.getValue().startsWith("org.springframework.")) {
+ int endPoint = i.getValue().indexOf(":", 19);
+ if (endPoint >= 0) {
+ mustContain = i.getValue().substring(19, endPoint).toLowerCase();
+ break;
+ }
+ }
+ }
+ }
+ if (mustContain != null) {
+ Iterator itr = dependency.getIdentifiers().iterator();
+ while (itr.hasNext()) {
+ Identifier i = itr.next();
+ if ("cpe".contains(i.getType())
+ && i.getValue() != null
+ && i.getValue().startsWith("cpe:/a:springsource:")
+ && !i.getValue().toLowerCase().contains(mustContain)) {
+ dependency.getIdentifiers().remove(i);
+ }
+
+ }
+ }
+ }
+
/**
*
* Intended to remove spurious CPE entries. By spurious we mean duplicate, less specific CPE entries.
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 2895bf6e2..97e934840 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
@@ -587,7 +587,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
groupid = groupid.substring(4);
}
foundSomething = true;
- dependency.getVendorEvidence().addEvidence("pom", "groupid", groupid, Confidence.HIGH);
+ dependency.getVendorEvidence().addEvidence("pom", "groupid", groupid, Confidence.HIGHEST);
dependency.getProductEvidence().addEvidence("pom", "groupid", groupid, Confidence.LOW);
addMatchingValues(classes, groupid, dependency.getVendorEvidence());
addMatchingValues(classes, groupid, dependency.getProductEvidence());
@@ -616,7 +616,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
artifactid = artifactid.substring(4);
}
foundSomething = true;
- dependency.getProductEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.HIGH);
+ dependency.getProductEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.HIGHEST);
dependency.getVendorEvidence().addEvidence("pom", "artifactid", artifactid, Confidence.LOW);
addMatchingValues(classes, artifactid, dependency.getVendorEvidence());
addMatchingValues(classes, artifactid, dependency.getProductEvidence());
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
index 08ddd9819..9ce32d38d 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
@@ -19,6 +19,7 @@ package org.owasp.dependencycheck.dependency;
import java.io.File;
import java.io.IOException;
+import java.io.Serializable;
import java.security.NoSuchAlgorithmException;
import java.util.Set;
import java.util.SortedSet;
@@ -35,7 +36,7 @@ import org.owasp.dependencycheck.utils.FileUtils;
*
* @author Jeremy Long
*/
-public class Dependency implements Comparable {
+public class Dependency implements Serializable, Comparable {
/**
* The logger.
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Evidence.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Evidence.java
index 6b8ad8bd6..594ffb613 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Evidence.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Evidence.java
@@ -17,12 +17,14 @@
*/
package org.owasp.dependencycheck.dependency;
+import java.io.Serializable;
+
/**
* Evidence is a piece of information about a Dependency.
*
* @author Jeremy Long
*/
-public class Evidence implements Comparable {
+public class Evidence implements Serializable, Comparable {
/**
* Creates a new Evidence object.
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/EvidenceCollection.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/EvidenceCollection.java
index 141370ab1..eae3f9750 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/EvidenceCollection.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/EvidenceCollection.java
@@ -17,6 +17,7 @@
*/
package org.owasp.dependencycheck.dependency;
+import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.Iterator;
@@ -36,7 +37,7 @@ import org.owasp.dependencycheck.utils.UrlStringUtils;
*
* @author Jeremy Long
*/
-public class EvidenceCollection implements Iterable {
+public class EvidenceCollection implements Serializable, Iterable {
/**
* The logger.
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Identifier.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Identifier.java
index 6e44ec613..3f64b2faa 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Identifier.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/dependency/Identifier.java
@@ -17,11 +17,13 @@
*/
package org.owasp.dependencycheck.dependency;
+import java.io.Serializable;
+
/**
*
* @author Jeremy Long
*/
-public class Identifier implements Comparable {
+public class Identifier implements Serializable, Comparable {
/**
* Constructs a new Identifier with the specified data.
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersion.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersion.java
index a11ca9123..b0ba88311 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersion.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/DependencyVersion.java
@@ -189,17 +189,23 @@ public class DependencyVersion implements Iterable, Comparable 3) {
- max = 3;
+ if (Math.abs(this.versionParts.size() - version.versionParts.size()) >= 3) {
+ return false;
}
+ final int max = (this.versionParts.size() < version.versionParts.size())
+ ? this.versionParts.size() : version.versionParts.size();
+
+ boolean ret = true;
for (int i = 0; i < max; i++) {
- if (this.versionParts.get(i) == null || !this.versionParts.get(i).equals(version.versionParts.get(i))) {
+ String thisVersion = this.versionParts.get(i);
+ String otherVersion = version.getVersionParts().get(i);
+ if (i >= 3) {
+ if (thisVersion.compareToIgnoreCase(otherVersion) >= 0) {
+ ret = false;
+ break;
+ }
+ } else if (!thisVersion.equals(otherVersion)) {
ret = false;
break;
}
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 3f469b64d..24bdb883a 100644
--- a/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml
+++ b/dependency-check-core/src/main/resources/dependencycheck-base-suppression.xml
@@ -17,4 +17,18 @@
com\.thoughtworks\.xstream:xstream:.*
cpe:/a:springsource:spring_framework
+
+
+ org.apache.velocity:velocity-tools:.*
+ cpe:/a:apache:struts
+
+
+
+ .*\.(jar|dll|exe|ear|war|pom)
+ cpe:/a:sandbox:sandbox
+
\ No newline at end of file
diff --git a/dependency-check-core/src/main/resources/templates/HtmlReport.vsl b/dependency-check-core/src/main/resources/templates/HtmlReport.vsl
index 52062ca6f..6c6d88120 100644
--- a/dependency-check-core/src/main/resources/templates/HtmlReport.vsl
+++ b/dependency-check-core/src/main/resources/templates/HtmlReport.vsl
@@ -33,8 +33,8 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
");
- sink.section1();
- sink.sectionTitle1();
- sink.text("Project: " + projectName);
- sink.sectionTitle1_();
- sink.date();
- final Date now = new Date();
- sink.text(DateFormat.getDateTimeInstance().format(now));
- sink.date_();
- sink.section1_();
- }
- //
-
- /**
- * Returns the maven settings proxy server.
- *
- * @param proxy the maven proxy
- * @return the proxy url
- */
- private String getMavenSettingsProxyServer(Proxy proxy) {
- return new StringBuilder(proxy.getProtocol()).append("://").append(proxy.getHost()).toString();
- }
-
- /**
- * Returns the maven proxy.
- *
- * @return the maven proxy
- */
- private Proxy getMavenProxy() {
- if (mavenSettings != null) {
- final List proxies = mavenSettings.getProxies();
- if (proxies != null && proxies.size() > 0) {
- if (mavenSettingsProxyId != null) {
- for (Proxy proxy : proxies) {
- if (mavenSettingsProxyId.equalsIgnoreCase(proxy.getId())) {
- return proxy;
- }
- }
- } else if (proxies.size() == 1) {
- return proxies.get(0);
- } else {
- throw new IllegalStateException("Ambiguous proxy definition");
- }
- }
- }
- return null;
+ return false;
}
+ //
/**
* Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system
* properties required to change the proxy url, port, and connection timeout.
@@ -796,34 +377,41 @@ public class DependencyCheckMojo extends AbstractMojo implements MavenMultiPageR
mojoProperties = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE);
Settings.mergeProperties(mojoProperties);
} catch (IOException ex) {
- logger.log(Level.WARNING, "Unable to load the dependency-check ant task.properties file.");
- logger.log(Level.FINE, null, ex);
+ LOGGER.log(Level.WARNING, "Unable to load the dependency-check ant task.properties file.");
+ LOGGER.log(Level.FINE, null, ex);
} finally {
if (mojoProperties != null) {
try {
mojoProperties.close();
} catch (IOException ex) {
- logger.log(Level.FINEST, null, ex);
+ LOGGER.log(Level.FINEST, null, ex);
}
}
}
Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
-
- if (proxyUrl != null && !proxyUrl.isEmpty()) {
- logger.warning("Deprecated configuration detected, proxyUrl will be ignored; use the maven settings to configure the proxy instead");
+ if (externalReport != null) {
+ LOGGER.warning("The 'externalReport' option was set; this configuration option has been removed. "
+ + "Please update the dependency-check-maven plugin's configuration");
}
+ if (proxyUrl != null && !proxyUrl.isEmpty()) {
+ LOGGER.warning("Deprecated configuration detected, proxyUrl will be ignored; use the maven settings "
+ + "to configure the proxy instead");
+ }
final Proxy proxy = getMavenProxy();
if (proxy != null) {
- Settings.setString(Settings.KEYS.PROXY_SERVER, getMavenSettingsProxyServer(proxy));
+ Settings.setString(Settings.KEYS.PROXY_SERVER, proxy.getHost());
Settings.setString(Settings.KEYS.PROXY_PORT, Integer.toString(proxy.getPort()));
final String userName = proxy.getUsername();
final String password = proxy.getPassword();
- if (userName != null && password != null) {
+ if (userName != null) {
Settings.setString(Settings.KEYS.PROXY_USERNAME, userName);
+ }
+ if (password != null) {
Settings.setString(Settings.KEYS.PROXY_PASSWORD, password);
}
+
}
if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
@@ -894,20 +482,47 @@ public class DependencyCheckMojo extends AbstractMojo implements MavenMultiPageR
if (cveUrl20Base != null && !cveUrl20Base.isEmpty()) {
Settings.setString(Settings.KEYS.CVE_SCHEMA_2_0, cveUrl20Base);
}
-
}
+ /**
+ * Returns the maven proxy.
+ *
+ * @return the maven proxy
+ */
+ private Proxy getMavenProxy() {
+ if (mavenSettings != null) {
+ final List proxies = mavenSettings.getProxies();
+ if (proxies != null && proxies.size() > 0) {
+ if (mavenSettingsProxyId != null) {
+ for (Proxy proxy : proxies) {
+ if (mavenSettingsProxyId.equalsIgnoreCase(proxy.getId())) {
+ return proxy;
+ }
+ }
+ } else if (proxies.size() == 1) {
+ return proxies.get(0);
+ } else {
+ LOGGER.warning("Multiple proxy defentiions exist in the Maven settings. In the dependency-check "
+ + "configuration set the maveSettingsProxyId so that the correct proxy will be used.");
+ throw new IllegalStateException("Ambiguous proxy definition");
+ }
+ }
+ }
+ return null;
+ }
+ //
+
/**
* Executes the dependency-check and generates the report.
*
* @throws MojoExecutionException if a maven exception occurs
* @throws MojoFailureException thrown if a CVSS score is found that is higher then the configured level
*/
- public void execute() throws MojoExecutionException, MojoFailureException {
- Engine engine = null;
+ @Override
+ protected void performExecute() throws MojoExecutionException, MojoFailureException {
try {
engine = executeDependencyCheck();
- generateExternalReports(engine, outputDirectory);
+ ReportingUtil.generateExternalReports(engine, outputDirectory, getProject().getName(), format);
if (this.showSummary) {
showSummary(engine.getDependencies());
}
@@ -915,59 +530,116 @@ public class DependencyCheckMojo extends AbstractMojo implements MavenMultiPageR
checkForFailure(engine.getDependencies());
}
} catch (DatabaseException ex) {
- logger.log(Level.SEVERE,
+ LOGGER.log(Level.SEVERE,
"Unable to connect to the dependency-check database; analysis has stopped");
- logger.log(Level.FINE, "", ex);
+ LOGGER.log(Level.FINE, "", ex);
+ }
+ }
+
+ @Override
+ protected void postExecute() throws MojoExecutionException, MojoFailureException {
+ try {
+ super.postExecute();
} finally {
- Settings.cleanup(true);
- if (engine != null) {
- engine.cleanup();
- }
+ cleanupEngine();
+ }
+ }
+
+ @Override
+ protected void postGenerate() throws MavenReportException {
+ try {
+ super.postGenerate();
+ } finally {
+ cleanupEngine();
}
}
/**
- * Generates the Dependency-Check Site Report.
- *
- * @param sink the sink to write the report to
- * @param locale the locale to use when generating the report
- * @throws MavenReportException if a Maven report exception occurs
+ * Calls engine.cleanup() to release resources.
*/
- public void generate(@SuppressWarnings("deprecation") org.codehaus.doxia.sink.Sink sink,
- Locale locale) throws MavenReportException {
- generate((Sink) sink, null, locale);
+ private void cleanupEngine() {
+ if (engine != null) {
+ engine.cleanup();
+ engine = null;
+ }
+ Settings.cleanup(true);
}
/**
* Generates the Dependency-Check Site Report.
*
- * @param sink the sink to write the report to
- * @param sinkFactory the sink factory
* @param locale the locale to use when generating the report
* @throws MavenReportException if a maven report exception occurs
*/
- public void generate(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException {
- Engine engine = null;
- try {
- engine = executeDependencyCheck();
- if (this.externalReport) {
- generateExternalReports(engine, reportOutputDirectory);
+ @Override
+ protected void executeNonAggregateReport(Locale locale) throws MavenReportException {
+
+ final List deps = readDataFile();
+ if (deps != null) {
+ try {
+ engine = initializeEngine();
+ engine.getDependencies().addAll(deps);
+ } catch (DatabaseException ex) {
+ final String msg = String.format("An unrecoverable exception with the dependency-check initialization occured while scanning %s",
+ getProject().getName());
+ throw new MavenReportException(msg, ex);
+ }
+ } else {
+ try {
+ engine = executeDependencyCheck();
+ } catch (DatabaseException ex) {
+ final String msg = String.format("An unrecoverable exception with the dependency-check scan occured while scanning %s",
+ getProject().getName());
+ throw new MavenReportException(msg, ex);
+ }
+ }
+ ReportingUtil.generateExternalReports(engine, getReportOutputDirectory(), getProject().getName(), format);
+ }
+
+ @Override
+ protected void executeAggregateReport(MavenProject project, Locale locale) throws MavenReportException {
+ List deps = readDataFile(project);
+ if (deps != null) {
+ try {
+ engine = initializeEngine();
+ engine.getDependencies().addAll(deps);
+ } catch (DatabaseException ex) {
+ final String msg = String.format("An unrecoverable exception with the dependency-check initialization occured while scanning %s",
+ project.getName());
+ throw new MavenReportException(msg, ex);
+ }
+ } else {
+ try {
+ engine = executeDependencyCheck(project);
+ } catch (DatabaseException ex) {
+ final String msg = String.format("An unrecoverable exception with the dependency-check scan occured while scanning %s",
+ project.getName());
+ throw new MavenReportException(msg, ex);
+ }
+ }
+ for (MavenProject child : getAllChildren(project)) {
+ deps = readDataFile(child);
+ if (deps == null) {
+ final String msg = String.format("Unable to include information on %s in the dependency-check aggregate report", child.getName());
+ LOGGER.severe(msg);
} else {
- generateMavenSiteReport(engine, sink);
- }
- } catch (DatabaseException ex) {
- logger.log(Level.SEVERE,
- "Unable to connect to the dependency-check database; analysis has stopped");
- logger.log(Level.FINE, "", ex);
- } finally {
- Settings.cleanup(true);
- if (engine != null) {
- engine.cleanup();
+ engine.getDependencies().addAll(deps);
}
}
+ final DependencyBundlingAnalyzer bundler = new DependencyBundlingAnalyzer();
+ try {
+ bundler.analyze(null, engine);
+ } catch (AnalysisException ex) {
+ LOGGER.log(Level.WARNING, "An error occured grouping the dependencies; duplicate entries may exist in the report", ex);
+ LOGGER.log(Level.FINE, "Bundling Exception", ex);
+ }
+ final File outputDir = getReportOutputDirectory(project);
+ if (outputDir != null) {
+ ReportingUtil.generateExternalReports(engine, outputDir, project.getName(), format);
+ }
}
- //
+ //
/**
* Returns the output name.
*
@@ -982,7 +654,7 @@ public class DependencyCheckMojo extends AbstractMojo implements MavenMultiPageR
} else if ("VULN".equalsIgnoreCase(this.format)) {
return "dependency-check-vulnerability";
} else {
- logger.log(Level.WARNING, "Unknown report format used during site generation.");
+ LOGGER.log(Level.WARNING, "Unknown report format used during site generation.");
return "dependency-check-report";
}
}
@@ -1003,25 +675,7 @@ public class DependencyCheckMojo extends AbstractMojo implements MavenMultiPageR
* @return the report name
*/
public String getName(Locale locale) {
- return name;
- }
-
- /**
- * Sets the Reporting output directory.
- *
- * @param directory the output directory
- */
- public void setReportOutputDirectory(File directory) {
- reportOutputDirectory = directory;
- }
-
- /**
- * Returns the output directory.
- *
- * @return the output directory
- */
- public File getReportOutputDirectory() {
- return reportOutputDirectory;
+ return "dependency-check";
}
/**
@@ -1031,28 +685,64 @@ public class DependencyCheckMojo extends AbstractMojo implements MavenMultiPageR
* @return the description
*/
public String getDescription(Locale locale) {
- return description;
+ return "A report providing details on any published "
+ + "vulnerabilities within project dependencies. This report is a best effort but may contain "
+ + "false positives and false negatives.";
}
/**
- * Returns whether this is an external report.
+ * Returns whether or not a report can be generated.
*
- * @return true or false;
- */
- public boolean isExternalReport() {
- return externalReport;
- }
-
- /**
- * Returns whether or not the plugin can generate a report.
- *
- * @return true
+ * @return true if a report can be generated; otherwise false
*/
public boolean canGenerateReport() {
- return true;
+ if (canGenerateAggregateReport() || (isAggregate() && isMultiModule())) {
+ return true;
+ }
+ if (canGenerateNonAggregateReport()) {
+ return true;
+ } else {
+ final String msg;
+ if (getProject().getArtifacts().size() > 0) {
+ msg = "No project dependencies exist in the included scope - dependency-check:check is unable to generate a report.";
+ } else {
+ msg = "No project dependencies exist - dependency-check:check is unable to generate a report.";
+ }
+ LOGGER.warning(msg);
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether or not a non-aggregate report can be generated.
+ *
+ * @return true if a non-aggregate report can be generated; otherwise false
+ */
+ @Override
+ protected boolean canGenerateNonAggregateReport() {
+ boolean ability = false;
+ for (Artifact a : getProject().getArtifacts()) {
+ if (!excludeFromScan(a)) {
+ ability = true;
+ break;
+ }
+ }
+ return ability;
+ }
+
+ /**
+ * Returns whether or not an aggregate report can be generated.
+ *
+ * @return true if an aggregate report can be generated; otherwise false
+ */
+ @Override
+ protected boolean canGenerateAggregateReport() {
+ return isAggregate() && isLastProject();
}
//
+ //
/**
* Checks to see if a vulnerability has been identified with a CVSS score that is above the threshold set in the
* configuration.
@@ -1120,7 +810,114 @@ public class DependencyCheckMojo extends AbstractMojo implements MavenMultiPageR
final String msg = String.format("%n%n"
+ "One or more dependencies were identified with known vulnerabilities:%n%n%s"
+ "%n%nSee the dependency-check report for more details.%n%n", summary.toString());
- logger.log(Level.WARNING, msg);
+ LOGGER.log(Level.WARNING, msg);
}
}
+ //
+
+ //
+ /**
+ * Writes the scan data to disk. This is used to serialize the scan data between the "check" and "aggregate" phase.
+ *
+ * @return the File object referencing the data file that was written
+ */
+ @Override
+ protected File writeDataFile() {
+ File file = null;
+ if (engine != null && getProject().getContextValue(this.getDataFileContextKey()) == null) {
+ file = new File(getProject().getBuild().getDirectory(), getDataFileName());
+ OutputStream os = null;
+ OutputStream bos = null;
+ ObjectOutputStream out = null;
+ try {
+ os = new FileOutputStream(file);
+ bos = new BufferedOutputStream(os);
+ out = new ObjectOutputStream(bos);
+ out.writeObject(engine.getDependencies());
+ out.flush();
+
+ //call reset to prevent resource leaks per
+ //https://www.securecoding.cert.org/confluence/display/java/SER10-J.+Avoid+memory+and+resource+leaks+during+serialization
+ out.reset();
+
+ } catch (IOException ex) {
+ LOGGER.log(Level.WARNING, "Unable to create data file used for report aggregation; "
+ + "if report aggregation is being used the results may be incomplete.");
+ LOGGER.log(Level.FINE, ex.getMessage(), ex);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException ex) {
+ LOGGER.log(Level.FINEST, "ignore", ex);
+ }
+ }
+ if (bos != null) {
+ try {
+ bos.close();
+ } catch (IOException ex) {
+ LOGGER.log(Level.FINEST, "ignore", ex);
+ }
+ }
+ if (os != null) {
+ try {
+ os.close();
+ } catch (IOException ex) {
+ LOGGER.log(Level.FINEST, "ignore", ex);
+ }
+ }
+ }
+ }
+ return file;
+ }
+
+ /**
+ * Reads the serialized scan data from disk. This is used to serialize the scan data between the "check" and
+ * "aggregate" phase.
+ *
+ * @return a Engine object populated with dependencies if the serialized data file exists; otherwise
+ * null is returned
+ */
+ protected List readDataFile() {
+ return readDataFile(getProject());
+ }
+
+ /**
+ * Reads the serialized scan data from disk. This is used to serialize the 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
+ */
+ protected List readDataFile(MavenProject project) {
+ final Object oPath = project.getContextValue(this.getDataFileContextKey());
+ if (oPath == null) {
+ return null;
+ }
+ List ret = null;
+ final String path = (String) oPath;
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(new FileInputStream(path));
+ ret = (List) ois.readObject();
+ } catch (FileNotFoundException ex) {
+ //TODO fix logging
+ LOGGER.log(Level.SEVERE, null, ex);
+ } catch (IOException ex) {
+ LOGGER.log(Level.SEVERE, null, ex);
+ } catch (ClassNotFoundException ex) {
+ LOGGER.log(Level.SEVERE, null, ex);
+ } finally {
+ if (ois != null) {
+ try {
+ ois.close();
+ } catch (IOException ex) {
+ LOGGER.log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+ return ret;
+ }
+ //
}
diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportAggregationMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportAggregationMojo.java
new file mode 100644
index 000000000..34dd4f750
--- /dev/null
+++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportAggregationMojo.java
@@ -0,0 +1,462 @@
+/*
+ * This file is part of dependency-check-maven.
+ *
+ * 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) 2014 Jeremy Long. All Rights Reserved.
+ */
+package org.owasp.dependencycheck.maven;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.reporting.MavenReport;
+import org.apache.maven.reporting.MavenReportException;
+
+/**
+ *
+ * This is an abstract reporting mojo that enables report aggregation. Some of the code in the this class was copied
+ * from the CoberturaReportMojo (http://mojo.codehaus.org/cobertura-maven-plugin/, version 2.6). The authors of the
+ * CoberturaReportMojo were Will Gwaltney and
+ * Joakim Erdfelt. There working example of how to do report aggregation was
+ * invaluable.
+ *
+ * An important point about using this abstract class is that it is intended for one to write some form of serialized
+ * data (via the {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#writeDataFile() }; note that the
+ * writeDataFile() function is called automatically after either {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#executeNonAggregateReport(org.apache.maven.doxia.sink.Sink,
+ * org.apache.maven.doxia.sink.SinkFactory, java.util.Locale)
+ * } or {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#executeAggregateReport(org.apache.maven.doxia.sink.Sink,
+ * org.apache.maven.doxia.sink.SinkFactory, java.util.Locale)
+ * } are called. When executeAggregateReport() is implemented, one can call {@link org.owasp.dependencycheck.maven.ReportAggregationMojo#getChildDataFiles()
+ * } to obtain a list of the data files to aggregate.
+ *
+ *
+ * @author Jeremy Long
+ */
+public abstract class ReportAggregationMojo extends AbstractMojo implements MavenReport {
+
+ /**
+ * The Maven Project Object.
+ */
+ @Component
+ private MavenProject project;
+
+ /**
+ * Logger field reference.
+ */
+ private static final Logger LOGGER = Logger.getLogger(ReportAggregationMojo.class.getName());
+
+ /**
+ * List of Maven project of the current build
+ */
+ @Parameter(readonly = true, required = true, property = "reactorProjects")
+ private List reactorProjects;
+
+ /**
+ * Generate aggregate reports in multi-module projects.
+ */
+ @Parameter(property = "aggregate", defaultValue = "false")
+ private boolean aggregate;
+
+ /**
+ * Sets whether or not the external report format should be used.
+ */
+ @Parameter(property = "metaFileName", defaultValue = "dependency-check.ser", required = true)
+ private String dataFileName;
+ /**
+ * Specifies the destination directory for the generated Dependency-Check report. This generally maps to
+ * "target/site".
+ */
+ @Parameter(property = "reportOutputDirectory", defaultValue = "${project.reporting.outputDirectory}", required = true)
+ private File reportOutputDirectory;
+
+ /**
+ * Sets the Reporting output directory.
+ *
+ * @param directory the output directory
+ */
+ @Override
+ public void setReportOutputDirectory(File directory) {
+ reportOutputDirectory = directory;
+ }
+
+ /**
+ * Returns the output directory.
+ *
+ * @return the output directory
+ */
+ @Override
+ public File getReportOutputDirectory() {
+ return reportOutputDirectory;
+ }
+
+ /**
+ * Returns the output directory for the given project.
+ *
+ * @param project the Maven project to get the output directory for
+ * @return the output directory for the given project
+ */
+ public File getReportOutputDirectory(MavenProject project) {
+ final Object o = project.getContextValue(getOutputDirectoryContextKey());
+ if (o != null && o instanceof File) {
+ return (File) o;
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether this is an external report. This method always returns true.
+ *
+ * @return true
+ */
+ @Override
+ public final boolean isExternalReport() {
+ return true;
+ }
+
+ /**
+ * The collection of child projects.
+ */
+ private final Map> projectChildren = new HashMap>();
+
+ /**
+ * Called before execute; allows for any setup that is needed. If this is overridden you must call
+ * super.preExecute().
+ *
+ * @throws MojoExecutionException thrown if there is an issue executing the mojo
+ * @throws MojoFailureException thrown if there is an issue executing the mojo
+ */
+ protected void preExecute() throws MojoExecutionException, MojoFailureException {
+ buildAggregateInfo();
+ }
+
+ /**
+ * Called when the mojo is being executed.
+ *
+ * @throws MojoExecutionException thrown if there is an issue executing the mojo
+ * @throws MojoFailureException thrown if there is an issue executing the mojo
+ */
+ protected abstract void performExecute() throws MojoExecutionException, MojoFailureException;
+
+ /**
+ * Runs after the mojo has executed. This implementation will call writeDataFile(). As such, it is
+ * important that if this method is overriden that super.postExecute() is called.
+ *
+ * @throws MojoExecutionException thrown if there is an issue executing the mojo
+ * @throws MojoFailureException thrown if there is an issue executing the mojo
+ */
+ protected void postExecute() throws MojoExecutionException, MojoFailureException {
+ final File written = writeDataFile();
+ if (written != null) {
+ project.setContextValue(getDataFileContextKey(), written.getAbsolutePath());
+ }
+ }
+
+ /**
+ * Returns the key used to store the path to the data file that is saved by writeDataFile(). This key
+ * is used in the MavenProject.(set|get)ContextValue.
+ *
+ * @return the key used to store the path to the data file
+ */
+ protected String getDataFileContextKey() {
+ return "dependency-check-path-" + this.getDataFileName();
+ }
+
+ /**
+ * Returns the key used to store the path to the output directory. When generating the report in the
+ * executeAggregateReport() the output directory should be obtained by using this key.
+ *
+ * @return the key used to store the path to the output directory
+ */
+ protected String getOutputDirectoryContextKey() {
+ return "dependency-output-dir-" + this.getDataFileName();
+ }
+
+ /**
+ * Is called by Maven to execute the mojo.
+ *
+ * @throws MojoExecutionException thrown if there is an issue executing the mojo
+ * @throws MojoFailureException thrown if there is an issue executing the mojo
+ */
+ public final void execute() throws MojoExecutionException, MojoFailureException {
+ try {
+ preExecute();
+ performExecute();
+ } finally {
+ postExecute();
+ }
+ }
+
+ /**
+ * Runs prior to the site report generation.
+ *
+ * @throws MavenReportException if a maven report exception occurs
+ */
+ protected void preGenerate() throws MavenReportException {
+ buildAggregateInfo();
+
+ project.setContextValue(getOutputDirectoryContextKey(), getReportOutputDirectory());
+ }
+
+ /**
+ * Executes after the site report has been generated.
+ *
+ * @throws MavenReportException if a maven report exception occurs
+ */
+ protected void postGenerate() throws MavenReportException {
+ final File written = writeDataFile();
+ if (written != null) {
+ project.setContextValue(getDataFileContextKey(), written.getAbsolutePath());
+ }
+ }
+
+ /**
+ * Generates the non aggregate report.
+ *
+ * @param locale the locale to use when generating the report
+ * @throws MavenReportException if a maven report exception occurs
+ */
+ protected abstract void executeNonAggregateReport(Locale locale) throws MavenReportException;
+
+ /**
+ * Generates the aggregate Site Report.
+ *
+ * @param project the maven project used to generate the aggregate report
+ * @param locale the locale to use when generating the report
+ * @throws MavenReportException if a maven report exception occurs
+ */
+ protected abstract void executeAggregateReport(MavenProject project, Locale locale) throws MavenReportException;
+
+ /**
+ * Generates the Dependency-Check Site Report.
+ *
+ * @param sink the sink to write the report to
+ * @param locale the locale to use when generating the report
+ * @throws MavenReportException if a maven report exception occurs
+ * @deprecated use {@link #generate(org.apache.maven.doxia.sink.Sink, java.util.Locale) instead.
+ */
+ @Deprecated
+ public final void generate(@SuppressWarnings("deprecation") org.codehaus.doxia.sink.Sink sink, Locale locale) throws MavenReportException {
+ generate((Sink) sink, locale);
+ }
+
+ /**
+ * Generates the Dependency-Check Site Report.
+ *
+ * @param sink the sink to write the report to
+ * @param locale the locale to use when generating the report
+ * @throws MavenReportException if a maven report exception occurs
+ */
+ public final void generate(Sink sink, Locale locale) throws MavenReportException {
+ try {
+ preGenerate();
+ if (canGenerateNonAggregateReport()) {
+ executeNonAggregateReport(locale);
+ }
+
+ if (canGenerateAggregateReport()) {
+ for (MavenProject proj : reactorProjects) {
+ if (!isMultiModule(proj)) {
+ continue;
+ }
+ executeAggregateReport(proj, locale);
+ }
+ }
+ } finally {
+ postGenerate();
+ }
+ }
+
+ /**
+ * Returns whether or not the mojo can generate a non-aggregate report for this project.
+ *
+ * @return true if a non-aggregate report can be generated, otherwise false
+ */
+ protected abstract boolean canGenerateNonAggregateReport();
+
+ /**
+ * Returns whether or not we can generate any aggregate reports at this time.
+ *
+ * @return true if an aggregate report can be generated, otherwise false
+ */
+ protected abstract boolean canGenerateAggregateReport();
+
+ /**
+ * Returns the name of the data file that contains the serialized data.
+ *
+ * @return the name of the data file that contains the serialized data
+ */
+ protected String getDataFileName() {
+ return dataFileName;
+ }
+
+ /**
+ * Writes the data file to disk in the target directory.
+ *
+ * @return the File object referencing the data file that was written
+ */
+ protected abstract File writeDataFile();
+
+ /**
+ * Collects the information needed for building aggregate reports.
+ */
+ private void buildAggregateInfo() {
+ // build parent-child map
+ for (MavenProject proj : reactorProjects) {
+ Set depList = projectChildren.get(proj.getParent());
+ if (depList == null) {
+ depList = new HashSet();
+ projectChildren.put(proj.getParent(), depList);
+ }
+ depList.add(proj);
+ }
+ }
+
+ /**
+ * Returns a list containing all the recursive, non-pom children of the given project, never null.
+ *
+ * @return a list of child projects
+ */
+ protected List getAllChildren() {
+ return getAllChildren(project);
+ }
+
+ /**
+ * Returns a list containing all the recursive, non-pom children of the given project, never null.
+ *
+ * @param parentProject the parent project to collect the child project references
+ * @return a list of child projects
+ */
+ protected List getAllChildren(MavenProject parentProject) {
+ final Set children = projectChildren.get(parentProject);
+ if (children == null) {
+ return Collections.emptyList();
+ }
+
+ final List result = new ArrayList();
+ for (MavenProject child : children) {
+ if (isMultiModule(child)) {
+ result.addAll(getAllChildren(child));
+ } else {
+ result.add(child);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a list of data files that were produced by the direct children of the given MavenProject.
+ *
+ * @param project the Maven project to obtain the child data files from
+ * @return a list of the data files
+ */
+ protected List getAllChildDataFiles(MavenProject project) {
+ final List children = getAllChildren(project);
+ return getDataFiles(children);
+ }
+
+ /**
+ * Returns any existing output files from the given list of projects.
+ *
+ * @param projects the list of projects to obtain the output files from
+ * @return a list of output files
+ */
+ protected List getDataFiles(List projects) {
+ final List files = new ArrayList();
+ for (MavenProject proj : projects) {
+ final Object path = project.getContextValue(getDataFileContextKey());
+ if (path == null) {
+ final String msg = String.format("Unable to aggregate data for '%s' - aggregate data file was not generated",
+ proj.getName());
+ LOGGER.warning(msg);
+ } else {
+ final File outputFile = new File((String) path);
+ if (outputFile.exists()) {
+ files.add(outputFile);
+ } else {
+ if (!isMultiModule(project)) {
+ final String msg = String.format("Unable to aggregate data for '%s' - missing data file '%s'",
+ proj.getName(), outputFile.getPath());
+ LOGGER.warning(msg);
+ }
+ }
+ }
+ }
+ return files;
+ }
+
+ /**
+ * Test if the project has pom packaging
+ *
+ * @param mavenProject Project to test
+ * @return true if it has a pom packaging; otherwise false
+ */
+ protected boolean isMultiModule(MavenProject mavenProject) {
+ return "pom".equals(mavenProject.getPackaging());
+ }
+
+ /**
+ * Test if the current project has pom packaging
+ *
+ * @return true if it has a pom packaging; otherwise false
+ */
+ protected boolean isMultiModule() {
+ return isMultiModule(project);
+ }
+
+ /**
+ * Check whether the current project is the last project in a multi-module build. If the maven build is not a
+ * multi-module project then this will always return true.
+ *
+ * @return true if the current project is the last project in a multi-module build; otherwise
+ * false
+ */
+ protected boolean isLastProject() {
+ return project.equals(reactorProjects.get(reactorProjects.size() - 1));
+ }
+
+ /**
+ * Returns whether or not the mojo is configured to perform report aggregation.
+ *
+ * @return true if report aggregation is enabled; otherwise false
+ */
+ public boolean isAggregate() {
+ return aggregate;
+ }
+
+ /**
+ * Returns a reference to the current project. This method is used instead of auto-binding the project via component
+ * annotation in concrete implementations of this. If the child has a @Component MavenProject project;
+ * defined then the abstract class (i.e. this class) will not have access to the current project (just the way Maven
+ * works with the binding).
+ *
+ * @return returns a reference to the current project
+ */
+ protected MavenProject getProject() {
+ return project;
+ }
+}
diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportingUtil.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportingUtil.java
new file mode 100644
index 000000000..d26e9b18e
--- /dev/null
+++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/ReportingUtil.java
@@ -0,0 +1,456 @@
+/*
+ * This file is part of dependency-check-maven.
+ *
+ * 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) 2014 Jeremy Long. All Rights Reserved.
+ */
+package org.owasp.dependencycheck.maven;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.maven.doxia.sink.Sink;
+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.dependency.Dependency;
+import org.owasp.dependencycheck.dependency.Evidence;
+import org.owasp.dependencycheck.dependency.Identifier;
+import org.owasp.dependencycheck.dependency.Reference;
+import org.owasp.dependencycheck.dependency.Vulnerability;
+import org.owasp.dependencycheck.dependency.VulnerableSoftware;
+import org.owasp.dependencycheck.reporting.ReportGenerator;
+
+/**
+ * A utility class that encapsulates the report generation for dependency-check-maven.
+ *
+ * @author Jeremy Long
+ */
+final class ReportingUtil {
+
+ /**
+ * Logger field reference.
+ */
+ private static final Logger LOGGER = Logger.getLogger(ReportingUtil.class.getName());
+
+ /**
+ * Empty private constructor for this utility class.
+ */
+ private ReportingUtil() {
+ }
+
+ /**
+ * Generates the reports for a given dependency-check engine.
+ *
+ * @param engine a dependency-check engine
+ * @param outDirectory the directory to write the reports to
+ * @param projectName the name of the project that a report is being generated for
+ * @param format the format of the report to generate
+ */
+ static void generateExternalReports(Engine engine, File outDirectory, String projectName, String format) {
+ DatabaseProperties prop = null;
+ CveDB cve = null;
+ try {
+ cve = new CveDB();
+ cve.open();
+ prop = cve.getDatabaseProperties();
+ } catch (DatabaseException ex) {
+ LOGGER.log(Level.FINE, "Unable to retrieve DB Properties", ex);
+ } finally {
+ if (cve != null) {
+ cve.close();
+ }
+ }
+ final ReportGenerator r = new ReportGenerator(projectName, engine.getDependencies(), engine.getAnalyzers(), prop);
+ try {
+ r.generateReports(outDirectory.getCanonicalPath(), format);
+ } catch (IOException ex) {
+ LOGGER.log(Level.SEVERE,
+ "Unexpected exception occurred during analysis; please see the verbose error log for more details.");
+ LOGGER.log(Level.FINE, null, ex);
+ } catch (Throwable ex) {
+ LOGGER.log(Level.SEVERE,
+ "Unexpected exception occurred during analysis; please see the verbose error log for more details.");
+ LOGGER.log(Level.FINE, null, ex);
+ }
+ }
+
+ /**
+ * Generates a dependency-check report using the Maven Site format.
+ *
+ * @param engine the engine used to scan the dependencies
+ * @param sink the sink to write the data to
+ * @param projectName the name of the project
+ */
+ static void generateMavenSiteReport(final Engine engine, Sink sink, String projectName) {
+ final List dependencies = engine.getDependencies();
+
+ writeSiteReportHeader(sink, projectName);
+ writeSiteReportTOC(sink, dependencies);
+
+ int cnt = 0;
+ for (Dependency d : dependencies) {
+ writeSiteReportDependencyHeader(sink, d);
+ cnt = writeSiteReportDependencyEvidenceUsed(d, cnt, sink);
+ cnt = writeSiteReportDependencyRelatedDependencies(d, cnt, sink);
+ writeSiteReportDependencyIdentifiers(d, sink);
+ writeSiteReportDependencyVulnerabilities(d, sink, cnt);
+ }
+ sink.body_();
+ }
+
+ //
+ /**
+ * Writes the vulnerabilities to the site report.
+ *
+ * @param d the dependency
+ * @param sink the sink to write the data to
+ * @param collapsibleHeaderCount the collapsible header count
+ */
+ private static void writeSiteReportDependencyVulnerabilities(Dependency d, Sink sink, int collapsibleHeaderCount) {
+ int cnt = collapsibleHeaderCount;
+ if (d.getVulnerabilities() != null && !d.getVulnerabilities().isEmpty()) {
+ for (Vulnerability v : d.getVulnerabilities()) {
+
+ sink.paragraph();
+ sink.bold();
+ try {
+ sink.link("http://web.nvd.nist.gov/view/vuln/detail?vulnId=" + URLEncoder.encode(v.getName(), "US-ASCII"));
+ sink.text(v.getName());
+ sink.link_();
+ sink.bold_();
+ } catch (UnsupportedEncodingException ex) {
+ sink.text(v.getName());
+ sink.bold_();
+ sink.lineBreak();
+ sink.text("http://web.nvd.nist.gov/view/vuln/detail?vulnId=" + v.getName());
+ }
+ sink.paragraph_();
+ sink.paragraph();
+ sink.text("Severity: ");
+ if (v.getCvssScore() < 4.0) {
+ sink.text("Low");
+ } else {
+ if (v.getCvssScore() >= 7.0) {
+ sink.text("High");
+ } else {
+ sink.text("Medium");
+ }
+ }
+ sink.lineBreak();
+ sink.text("CVSS Score: " + v.getCvssScore());
+ if (v.getCwe() != null && !v.getCwe().isEmpty()) {
+ sink.lineBreak();
+ sink.text("CWE: ");
+ sink.text(v.getCwe());
+ }
+ sink.paragraph_();
+ sink.paragraph();
+ sink.text(v.getDescription());
+ if (v.getReferences() != null && !v.getReferences().isEmpty()) {
+ sink.list();
+ for (Reference ref : v.getReferences()) {
+ sink.listItem();
+ sink.text(ref.getSource());
+ sink.text(" - ");
+ sink.link(ref.getUrl());
+ sink.text(ref.getName());
+ sink.link_();
+ sink.listItem_();
+ }
+ sink.list_();
+ }
+ sink.paragraph_();
+ if (v.getVulnerableSoftware() != null && !v.getVulnerableSoftware().isEmpty()) {
+ sink.paragraph();
+
+ cnt += 1;
+ sink.rawText("Vulnerable Software [-]");
+ sink.rawText("");
+ sink.list();
+ for (VulnerableSoftware vs : v.getVulnerableSoftware()) {
+ sink.listItem();
+ try {
+ sink.link("http://web.nvd.nist.gov/view/vuln/search-results?cpe=" + URLEncoder.encode(vs.getName(), "US-ASCII"));
+ sink.text(vs.getName());
+ sink.link_();
+ if (vs.hasPreviousVersion()) {
+ sink.text(" and all previous versions.");
+ }
+ } catch (UnsupportedEncodingException ex) {
+ sink.text(vs.getName());
+ if (vs.hasPreviousVersion()) {
+ sink.text(" and all previous versions.");
+ }
+ sink.text(" (http://web.nvd.nist.gov/view/vuln/search-results?cpe=" + vs.getName() + ")");
+ }
+
+ sink.listItem_();
+ }
+ sink.list_();
+ sink.rawText("
");
+ sink.paragraph_();
+ }
+ }
+ }
+ }
+
+ /**
+ * Writes the identifiers to the site report.
+ *
+ * @param d the dependency
+ * @param sink the sink to write the data to
+ */
+ private static void writeSiteReportDependencyIdentifiers(Dependency d, Sink sink) {
+ if (d.getIdentifiers() != null && !d.getIdentifiers().isEmpty()) {
+ sink.sectionTitle4();
+ sink.text("Identifiers");
+ sink.sectionTitle4_();
+ sink.list();
+ for (Identifier i : d.getIdentifiers()) {
+ sink.listItem();
+ sink.text(i.getType());
+ sink.text(": ");
+ if (i.getUrl() != null && i.getUrl().length() > 0) {
+ sink.link(i.getUrl());
+ sink.text(i.getValue());
+ sink.link_();
+ } else {
+ sink.text(i.getValue());
+ }
+ if (i.getDescription() != null && i.getDescription().length() > 0) {
+ sink.lineBreak();
+ sink.text(i.getDescription());
+ }
+ sink.listItem_();
+ }
+ sink.list_();
+ }
+ }
+
+ /**
+ * Writes the related dependencies to the site report.
+ *
+ * @param d the dependency
+ * @param sink the sink to write the data to
+ * @param collapsibleHeaderCount the collapsible header count
+ * @return the collapsible header count
+ */
+ private static int writeSiteReportDependencyRelatedDependencies(Dependency d, int collapsibleHeaderCount, Sink sink) {
+ int cnt = collapsibleHeaderCount;
+ if (d.getRelatedDependencies() != null && !d.getRelatedDependencies().isEmpty()) {
+ cnt += 1;
+ sink.sectionTitle4();
+ sink.rawText("Related Dependencies [+]");
+ sink.sectionTitle4_();
+ sink.rawText("");
+ sink.list();
+ for (Dependency r : d.getRelatedDependencies()) {
+ sink.listItem();
+ sink.text(r.getFileName());
+ sink.list();
+ writeListItem(sink, "File Path: " + r.getFilePath());
+ writeListItem(sink, "SHA1: " + r.getSha1sum());
+ writeListItem(sink, "MD5: " + r.getMd5sum());
+ sink.list_();
+ sink.listItem_();
+ }
+ sink.list_();
+ sink.rawText("
");
+ }
+ return cnt;
+ }
+
+ /**
+ * Writes the evidence used to the site report.
+ *
+ * @param d the dependency
+ * @param sink the sink to write the data to
+ * @param collapsibleHeaderCount the collapsible header count
+ * @return the collapsible header count
+ */
+ private static int writeSiteReportDependencyEvidenceUsed(Dependency d, int collapsibleHeaderCount, Sink sink) {
+ int cnt = collapsibleHeaderCount;
+ final Set evidence = d.getEvidenceForDisplay();
+ if (evidence != null && evidence.size() > 0) {
+ cnt += 1;
+ sink.sectionTitle4();
+ sink.rawText("Evidence Collected [+]");
+ sink.sectionTitle4_();
+ sink.rawText("");
+ sink.table();
+ sink.tableRow();
+ writeTableHeaderCell(sink, "Source");
+ writeTableHeaderCell(sink, "Name");
+ writeTableHeaderCell(sink, "Value");
+ sink.tableRow_();
+ for (Evidence e : evidence) {
+ sink.tableRow();
+ writeTableCell(sink, e.getSource());
+ writeTableCell(sink, e.getName());
+ writeTableCell(sink, e.getValue());
+ sink.tableRow_();
+ }
+ sink.table_();
+ sink.rawText("
");
+ }
+ return cnt;
+ }
+
+ /**
+ * Writes the dependency header to the site report.
+ *
+ * @param d the dependency
+ * @param sink the sink to write the data to
+ */
+ private static void writeSiteReportDependencyHeader(Sink sink, Dependency d) {
+ sink.sectionTitle2();
+ sink.anchor("sha1" + d.getSha1sum());
+ sink.text(d.getFileName());
+ sink.anchor_();
+ sink.sectionTitle2_();
+ if (d.getDescription() != null && d.getDescription().length() > 0) {
+ sink.paragraph();
+ sink.bold();
+ sink.text("Description: ");
+ sink.bold_();
+ sink.text(d.getDescription());
+ sink.paragraph_();
+ }
+ if (d.getLicense() != null && d.getLicense().length() > 0) {
+ sink.paragraph();
+ sink.bold();
+ sink.text("License: ");
+ sink.bold_();
+ if (d.getLicense().startsWith("http://") && !d.getLicense().contains(" ")) {
+ sink.link(d.getLicense());
+ sink.text(d.getLicense());
+ sink.link_();
+ } else {
+ sink.text(d.getLicense());
+ }
+ sink.paragraph_();
+ }
+ }
+
+ /**
+ * Adds a list item to the site report.
+ *
+ * @param sink the sink to write the data to
+ * @param text the text to write
+ */
+ private static void writeListItem(Sink sink, String text) {
+ sink.listItem();
+ sink.text(text);
+ sink.listItem_();
+ }
+
+ /**
+ * Adds a table cell to the site report.
+ *
+ * @param sink the sink to write the data to
+ * @param text the text to write
+ */
+ private static void writeTableCell(Sink sink, String text) {
+ sink.tableCell();
+ sink.text(text);
+ sink.tableCell_();
+ }
+
+ /**
+ * Adds a table header cell to the site report.
+ *
+ * @param sink the sink to write the data to
+ * @param text the text to write
+ */
+ private static void writeTableHeaderCell(Sink sink, String text) {
+ sink.tableHeaderCell();
+ sink.text(text);
+ sink.tableHeaderCell_();
+ }
+
+ /**
+ * Writes the TOC for the site report.
+ *
+ * @param sink the sink to write the data to
+ * @param dependencies the dependencies that are being reported on
+ */
+ private static void writeSiteReportTOC(Sink sink, final List dependencies) {
+ sink.list();
+ for (Dependency d : dependencies) {
+ sink.listItem();
+ sink.link("#sha1" + d.getSha1sum());
+ sink.text(d.getFileName());
+ sink.link_();
+ if (!d.getVulnerabilities().isEmpty()) {
+ sink.rawText(" •");
+ }
+ if (!d.getRelatedDependencies().isEmpty()) {
+ sink.list();
+ for (Dependency r : d.getRelatedDependencies()) {
+ writeListItem(sink, r.getFileName());
+ }
+ sink.list_();
+ }
+ sink.listItem_();
+ }
+ sink.list_();
+ }
+
+ /**
+ * Writes the site report header.
+ *
+ * @param sink the sink to write the data to
+ * @param projectName the name of the project
+ */
+ private static void writeSiteReportHeader(Sink sink, String projectName) {
+ sink.head();
+ sink.title();
+ sink.text("Dependency-Check Report: " + projectName);
+ sink.title_();
+ sink.head_();
+ sink.body();
+ sink.rawText("");
+ sink.section1();
+ sink.sectionTitle1();
+ sink.text("Project: " + projectName);
+ sink.sectionTitle1_();
+ sink.date();
+ final Date now = new Date();
+ sink.text(DateFormat.getDateTimeInstance().format(now));
+ sink.date_();
+ sink.section1_();
+ }
+ //
+
+}
diff --git a/dependency-check-maven/src/site/markdown/configuration.md b/dependency-check-maven/src/site/markdown/configuration.md
index 672d4c774..cccc733fb 100644
--- a/dependency-check-maven/src/site/markdown/configuration.md
+++ b/dependency-check-maven/src/site/markdown/configuration.md
@@ -4,8 +4,8 @@ The following properties can be set on the dependency-check-maven plugin.
Property | Description | Default Value
---------------------|------------------------------------|------------------
+aggregate | Sets whether report aggregation will be performed for multi-module site reports. This option only affects the report generation when configured within the reporting section. | false
autoUpdate | Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. | true
-externalReport | When using as a Site plugin this parameter sets whether or not the external report format should be used. | false
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'
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
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
@@ -31,7 +31,7 @@ jarAnalyzer | Sets whether Jar Analyzer will be used.
nexusAnalyzerEnabled | Sets whether Nexus Analyzer will be used. | true
nexusUrl | Defines the Nexus URL. | https://repository.sonatype.org/service/local/
nexusUsesProxy | Whether or not the defined proxy should be used when connecting to Nexus. | true
-nuspecAnalyzerEnabled | Sets whether or not the .NET Nuget Nuspec Analyzer will be used. | true
+nuspecAnalyzerEnabled | Sets whether or not the .NET Nuget Nuspec Analyzer will be used. | true
assemblyAnalyzerEnabled | Sets whether or not the .NET Assembly Analyzer should be used. | true
pathToMono | The path to Mono for .NET assembly analysis on non-windows systems |
@@ -40,21 +40,27 @@ Advanced Configuration
The following properties can be configured in the plugin. However, they are less frequently changed. One exception
may be the cvedUrl properties, which can be used to host a mirror of the NVD within an enterprise environment.
-Property | Description | Default Value
----------------------|-------------------------------------------------------------------------|------------------
-cveUrl12Modified | URL for the modified CVE 1.2 | http://nvd.nist.gov/download/nvdcve-modified.xml
-cveUrl20Modified | URL for the modified CVE 2.0 | http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-modified.xml
-cveUrl12Base | Base URL for each year's CVE 1.2, the %d will be replaced with the year | http://nvd.nist.gov/download/nvdcve-%d.xml
-cveUrl20Base | Base URL for each year's CVE 2.0, the %d will be replaced with the year | http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml
-connectionTimeout | The URL Connection Timeout. |
-dataDirectory | Data directory to hold SQL CVEs contents. This should generally not be changed. |
+Property | Description | Default Value
+---------------------|--------------------------------------------------------------------------|------------------
+cveUrl12Modified | URL for the modified CVE 1.2. | http://nvd.nist.gov/download/nvdcve-modified.xml
+cveUrl20Modified | URL for the modified CVE 2.0. | http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-modified.xml
+cveUrl12Base | Base URL for each year's CVE 1.2, the %d will be replaced with the year. | http://nvd.nist.gov/download/nvdcve-%d.xml
+cveUrl20Base | Base URL for each year's CVE 2.0, the %d will be replaced with the year. | http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%d.xml
+connectionTimeout | Sets the URL Connection Timeout used when downloading external data. |
+dataDirectory | Sets the data directory to hold SQL CVEs contents. This should generally not be changed. |
databaseDriverName | The name of the database driver. Example: org.h2.Driver. |
databaseDriverPath | The path to the database driver JAR file; only used if the driver is not in the class path. |
connectionString | The connection string used to connect to the database. |
databaseUser | The username used when connecting to the database. |
databasePassword | The password used when connecting to the database. |
-
+metaFileName | Sets the name of the file to use for storing the metadata about the project. | dependency-check.ser
Proxy Configuration
====================
-Use [Maven's settings](https://maven.apache.org/settings.html#Proxies) to configure a proxy server.
+Use [Maven's settings](https://maven.apache.org/settings.html#Proxies) to configure a proxy server. If multiple proxies
+are configured in the Maven settings file you must tell dependency-check which proxy to use with the following property:
+
+Property | Description | Default Value
+---------------------|--------------------------------------------------------------------------------------|------------------
+mavenSettingsProxyId | The id for the proxy, configured via settings.xml, that dependency-check should use. |
+
diff --git a/dependency-check-maven/src/site/markdown/usage.md.vm b/dependency-check-maven/src/site/markdown/usage.md.vm
index c04b26e9f..6c2c7e10c 100644
--- a/dependency-check-maven/src/site/markdown/usage.md.vm
+++ b/dependency-check-maven/src/site/markdown/usage.md.vm
@@ -17,7 +17,9 @@ Create the DependencyCheck-report.html in the target directory
```xml
+ ...
+ ...
...
@@ -41,11 +43,48 @@ Create the DependencyCheck-report.html in the target directory
```
$H$H$H Example 2:
+Create an aggregated dependency-check report within the site
+
+```xml
+
+ ...
+
+ ...
+
+ ...
+
+
+ org.owasp
+ dependency-check-maven
+ ${project.version}
+
+ true
+
+
+
+
+ check
+
+
+
+
+
+ ...
+
+ ...
+
+ ...
+
+```
+
+$H$H$H Example 3:
Create the DependencyCheck-report.html and fail the build for CVSS greater then 8
```xml
+ ...
+ ...
...
@@ -71,44 +110,14 @@ Create the DependencyCheck-report.html and fail the build for CVSS greater then
```
-$H$H$H Example 3:
-Create the dependency-check report within the site
-
-```xml
-
-
-
- ...
-
- org.apache.maven.plugins
- maven-site-plugin
-
-
-
- org.owasp
- dependency-check-maven
- ${project.version}
-
- false
-
-
-
-
-
- ...
-
- ...
-
- ...
-
-```
-
$H$H$H Example 4:
-Create the DependencyCheck-report.html and skip artifacts no bundled in distribution (Provided and Runtime scope)
+Create the DependencyCheck-report.html and skip artifacts not bundled in distribution (Provided and Runtime scope)
```xml
+ ...
+ ...
...
@@ -140,7 +149,9 @@ Create the DependencyCheck-report.html and use internal mirroring of CVE content
```xml
+ ...
+ ...
...
diff --git a/dependency-check-utils/pom.xml b/dependency-check-utils/pom.xml
index 1200559cf..a71065a60 100644
--- a/dependency-check-utils/pom.xml
+++ b/dependency-check-utils/pom.xml
@@ -21,7 +21,7 @@ Copyright (c) 2014 - Jeremy Long. All Rights Reserved.
org.owasp
dependency-check-parent
- 1.2.4-SNAPSHOT
+ 1.2.5-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 bc497b8d3..0224943a2 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
@@ -63,8 +63,22 @@ public final class Checksum {
try {
fis = new FileInputStream(file);
FileChannel ch = fis.getChannel();
- MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
- digest.update(byteBuffer);
+ long remainingToRead = file.length();
+ long start = 0;
+ while (remainingToRead > 0) {
+ long amountToRead;
+ if (remainingToRead > Integer.MAX_VALUE) {
+ remainingToRead -= Integer.MAX_VALUE;
+ amountToRead = Integer.MAX_VALUE;
+ } else {
+ amountToRead = remainingToRead;
+ remainingToRead = 0;
+ }
+ MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, start, amountToRead);
+ digest.update(byteBuffer);
+ start += amountToRead;
+ }
+
// BufferedInputStream bis = new BufferedInputStream(fis);
// DigestInputStream dis = new DigestInputStream(bis, digest);
// //yes, we are reading in a buffer for performance reasons - 1 byte at a time is SLOW
diff --git a/pom.xml b/pom.xml
index e1b968423..751fefd83 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,4 +1,3 @@
-