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 5be1c2d92..6413017c1 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
@@ -120,7 +120,7 @@ public final class CliParser {
Format.valueOf(format);
} catch (IllegalArgumentException ex) {
final String msg = String.format("An invalid 'format' of '%s' was specified. "
- + "Supported output formats are XML, HTML, VULN, or ALL", format);
+ + "Supported output formats are XML, JSON, HTML, VULN, or ALL", format);
throw new ParseException(msg);
}
}
@@ -262,7 +262,7 @@ public final class CliParser {
.build();
final Option outputFormat = Option.builder(ARGUMENT.OUTPUT_FORMAT_SHORT).argName("format").hasArg().longOpt(ARGUMENT.OUTPUT_FORMAT)
- .desc("The output format to write to (XML, HTML, VULN, ALL). The default is HTML.")
+ .desc("The output format to write to (XML, JSON, HTML, VULN, ALL). The default is HTML.")
.build();
final Option verboseLog = Option.builder(ARGUMENT.VERBOSE_LOG_SHORT).argName("file").hasArg().longOpt(ARGUMENT.VERBOSE_LOG)
diff --git a/dependency-check-core/pom.xml b/dependency-check-core/pom.xml
index dc7590c6c..d7b44ac21 100644
--- a/dependency-check-core/pom.xml
+++ b/dependency-check-core/pom.xml
@@ -340,6 +340,10 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
com.sun.mail
mailapi
+
+ com.google.code.gson
+ gson
+
org.apache.maven.scm
@@ -470,6 +474,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
test
true
+
@@ -672,13 +677,6 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
test
true
-
- com.google.code.gson
- gson
- 2.3.1
- test
- true
-
com.google.gerrit
gerrit-extension-api
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java
index 4a7a2b491..9c1d905ce 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java
@@ -80,4 +80,16 @@ public class EscapeTool {
}
return StringEscapeUtils.escapeXml11(text);
}
+
+ /**
+ * JSON Encodes the provded text
+ * @param text the text to encode
+ * @return the JSON encoded text
+ */
+ public String json(String text) {
+ if (text == null || text.isEmpty()) {
+ return text;
+ }
+ return StringEscapeUtils.escapeJson(text);
+ }
}
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 a97aef3fa..e8ebcd779 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
@@ -17,17 +17,14 @@
*/
package org.owasp.dependencycheck.reporting;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
+import java.io.*;
+import java.nio.file.*;
import java.util.List;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
@@ -78,7 +75,11 @@ public class ReportGenerator {
/**
* Generate HTML Vulnerability report.
*/
- VULN
+ VULN,
+ /**
+ * Generate JSON report.
+ */
+ JSON
}
/**
* The Velocity Engine.
@@ -164,6 +165,9 @@ public class ReportGenerator {
if (format == Format.VULN || format == Format.ALL) {
generateReport("VulnerabilityReport", outputStream);
}
+ if (format == Format.JSON || format == Format.ALL) {
+ generateReport("JsonReport", outputStream);
+ }
}
/**
@@ -178,12 +182,27 @@ public class ReportGenerator {
if (format == Format.XML || format == Format.ALL) {
generateReport("XmlReport", outputDir + File.separator + "dependency-check-report.xml");
}
+ if (format == Format.JSON || format == Format.ALL) {
+ generateReport("JsonReport", outputDir + File.separator + "dependency-check-report.json");
+ try {
+ Path resultPath = Paths.get(outputDir + File.separator + "dependency-check-report.json");
+ String content = new String(Files.readAllBytes(resultPath));
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ JsonParser jp = new JsonParser();
+ JsonElement je = jp.parse(content);
+ String prettyJson = gson.toJson(je);
+ Files.write(Paths.get(outputDir + File.separator + "dependency-check-report.json"), prettyJson.getBytes(), StandardOpenOption.WRITE);
+ } catch (IOException e) {
+ LOGGER.error("Unable to generate pretty report, got error: ", e.getMessage());
+ }
+ }
if (format == Format.HTML || format == Format.ALL) {
generateReport("HtmlReport", outputDir + File.separator + "dependency-check-report.html");
}
if (format == Format.VULN || format == Format.ALL) {
generateReport("VulnerabilityReport", outputDir + File.separator + "dependency-check-vulnerability.html");
}
+
}
/**
@@ -198,7 +217,7 @@ public class ReportGenerator {
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)$")) {
+ if (format.matches("^(XML|HTML|VULN|JSON|ALL)$")) {
if ("XML".equalsIgnoreCase(format)) {
if (pathToCheck.endsWith(".xml")) {
generateReport("XmlReport", outputDir);
@@ -220,6 +239,13 @@ public class ReportGenerator {
generateReports(outputDir, Format.VULN);
}
}
+ if ("JSON".equalsIgnoreCase(format)) {
+ if (pathToCheck.endsWith(".json")) {
+ generateReport("JsonReport", outputDir);
+ } else {
+ generateReports(outputDir, Format.JSON);
+ }
+ }
if ("ALL".equalsIgnoreCase(format)) {
generateReports(outputDir, Format.ALL);
}
diff --git a/dependency-check-core/src/main/resources/templates/JsonReport.vsl b/dependency-check-core/src/main/resources/templates/JsonReport.vsl
new file mode 100644
index 000000000..d83feba51
--- /dev/null
+++ b/dependency-check-core/src/main/resources/templates/JsonReport.vsl
@@ -0,0 +1,198 @@
+{
+ "analysis": {
+ "scanInfo": {
+ "engineVersion": "$version",
+ "dataSource": [
+ #foreach($prop in $properties.getMetaData().entrySet())
+ #if($foreach.count > 1),#end{
+ "name": "$enc.json($prop.key)",
+ "timestamp": "$enc.json($prop.value)"
+ }
+ #end
+ ]
+ },
+ "projectInfo": {
+ "name": "$enc.json($applicationName)",
+ "reportDate": "$scanDateXML",
+ "credits": "This report contains data retrieved from the National Vulnerability Database: http://nvd.nist.gov"
+ },
+ "dependencies": [
+ #foreach($dependency in $dependencies)#if($foreach.count > 1),#end{
+ "fileName": "$enc.json($dependency.DisplayFileName)",
+ "filePath": "$enc.json($dependency.FilePath)",
+ "md5": "$enc.json($dependency.Md5sum)",
+ "sha1": "$enc.json($dependency.Sha1sum)"
+ #if($dependency.description),"description": "$enc.json($dependency.description)"#end
+ #if($dependency.license),"license": "$enc.json($dependency.license)"#end
+ #if ($dependency.getRelatedDependencies().size()>0)
+ ,"relatedDependencies": [
+ #foreach($related in $dependency.getRelatedDependencies()) #if($foreach.count > 1),#end {
+ "filePath": "$enc.json($related.FilePath)",
+ "sha1": "$enc.json($related.Sha1sum)",
+ "md5": "$enc.json($related.Md5sum)"#if($related.getIdentifiers()),#end
+ "identifiers": [
+ #foreach($id in $related.getIdentifiers())
+ #if ($id.type=="maven")
+ {
+ "type": "$enc.json($id.type)",
+ "name": "$id.value"
+ #if( $id.url ),"url": "$enc.json($id.url)"#end
+ #if ($id.notes),"notes": "$enc.json($id.notes)"#end
+ }
+ #end
+ #end
+ ]
+ }
+ #end
+ ]
+ #end
+
+ ,"evidenceCollected": {
+
+ "vendorEvidence": [
+ #foreach($evidence in $dependency.getVendorEvidence())
+ #if($foreach.count > 1),#end{
+ "type": "vendor",
+ "confidence": "$enc.json($evidence.getConfidence().toString())",
+ "source": "$enc.json($evidence.getSource())",
+ "name": "$enc.json($evidence.getName())",
+ "value": "$enc.json($evidence.getValue().trim())"
+ }
+ #end
+ ],
+ "productEvidence": [
+ #foreach($evidence in $dependency.getProductEvidence())
+ #if($foreach.count > 1),#end{
+ "type": "product",
+ "confidence": "$enc.json($evidence.getConfidence().toString())",
+ "source": "$enc.json($evidence.getSource())",
+ "name": "$enc.json($evidence.getName())",
+ "value": "$enc.json($evidence.getValue().trim())"
+ }
+ #end
+ ],
+ "versionEvidence": [
+ #foreach($evidence in $dependency.getVersionEvidence())
+ #if($foreach.count > 1),#end{
+ "type": "version",
+ "confidence": "$enc.json($evidence.getConfidence().toString())",
+ "source": "$enc.json($evidence.getSource())",
+ "name": "$enc.json($evidence.getName())",
+ "value": "$enc.json($evidence.getValue().trim())"
+ }
+ #end
+ ]
+
+ },
+ "identifiers": [
+ #foreach($id in $dependency.getIdentifiers())#if($foreach.count > 1),#end{
+ "name": "$id.value",
+ "type": "$enc.json($id.type)",
+ #if($id.confidence)"confidence": "$id.confidence",#end
+ #if($id.url)"url": "$enc.json($id.url)",#end
+ #if($id.description )"description": "$enc.json($id.description)",#end
+ #if ($id.notes)"notes": "$enc.json($id.notes)",#end
+ "suppressedIdentifiers": [
+ #foreach($id in $dependency.getSuppressedIdentifiers())
+ #if($foreach.count > 1),#end{
+ "type": "$enc.json($id.type)",
+ #if($id.confidence)"confidence": "$id.confidence",#end
+ "name": "$id.value",
+ #if($id.url)"url": "$enc.json($id.url),"#end
+ #if($id.description)"description": "$enc.json($id.description)",#end
+ #if ($id.notes)"notes": "$enc.json($id.notes)"#end
+ }
+ #end
+ ]
+ }
+ #end
+ ]
+
+ #if($dependency.getVulnerabilities().size()>0 || $dependency.getSuppressedVulnerabilities().size()>0)
+ ,"vulnerabilities": [
+ #foreach($vuln in $dependency.getVulnerabilities())
+ #if($foreach.count > 1),#end {
+ "name": "$enc.json($vuln.name)",
+ "cvssScore": "$vuln.cvssScore",
+ "cvssAccessVector": "$enc.json($vuln.cvssAccessVector)",
+ "cvssAccessComplexity": "$enc.json($vuln.cvssAccessComplexity)",
+ "cvssAuthenticationr": "$enc.json($vuln.cvssAuthentication)",
+ "cvssConfidentialImpact": "$enc.json($vuln.cvssConfidentialityImpact)",
+ "cvssIntegrityImpact": "$enc.json($vuln.cvssIntegrityImpact)",
+ "cvssAvailabilityImpact": "$enc.json($vuln.cvssAvailabilityImpact)",
+ #if ($vuln.cvssScore<4.0)
+ "severity": "Low",
+ #elseif ($vuln.cvssScore>=7.0)
+ "severity": "High",
+ #else
+ "severity": "Medium",
+ #end
+ #if($vuln.cwe)"cwe": "$enc.json($vuln.cwe)",#end
+ "description": "$enc.json($vuln.description)",
+ #if ($vuln.notes)"notes": "$enc.json($vuln.notes)"#end
+ "references": [
+ #foreach($ref in $vuln.getReferences())
+ #if($foreach.count > 1),#end {
+ "source": "$enc.json($ref.source)",
+ "url": "$enc.json($ref.url)",
+ "name": "$enc.json($ref.name)"
+ }
+ #end
+ ],
+ "vulnerableSoftware": [
+ #foreach($vs in $vuln.getVulnerableSoftware())
+ #if($foreach.count > 1),#end {
+ #if($vs.hasPreviousVersion()) "allPreviousVersion": "true",#end
+ "software": "$enc.json($vs.name)"
+ }
+ #end
+ ]
+ }#end
+ ]
+ #end
+
+ #if($dependency.getSuppressedVulnerabilities().size()>0 || $dependency.getSuppressedVulnerabilities().size()>0)
+ ,"suppressedVulnerabilities": [
+ #foreach($vuln in $dependency.getSuppressedVulnerabilities())#if($foreach.count > 1),#end {
+ "name": "$enc.json($vuln.name)",
+ "cvssScore": "$vuln.cvssScore",
+ "cvssAccessVector": "$enc.json"($vuln.cvssAccessVector),
+ "cvssAccessComplexity": "$enc.json"($vuln.cvssAccessComplexity),
+ "cvssAuthenticationr": "$enc.json"($vuln.cvssAuthentication),
+ "cvssConfidentialImpact": "$enc.json"($vuln.cvssConfidentialityImpact),
+ "cvssIntegrityImpact": "$enc.json"($vuln.cvssIntegrityImpact),
+ "cvssAvailabilityImpact": "$enc.json"($vuln.cvssAvailabilityImpact),
+ #if ($vuln.cvssScore<4.0) "severity": "Low",
+ #elseif ($vuln.cvssScore>=7.0) "severity": "High",
+ #else "severity": "Medium",
+ #end
+ #if ($vuln.cwe)"cwe": "$enc.json($vuln.cwe)",#end
+ "description": "$enc.json($vuln.description)"
+ #if ($vuln.notes),"notes": "$enc.json($vuln.notes)"#end
+ ,"references" [
+ #foreach($ref in $vuln.getReferences())
+ #if($foreach.count > 1),#end {
+ "source": "$enc.json($ref.source)",
+ "url": "$enc.json($ref.url)",
+ "name": "$enc.json($ref.name)"
+ }
+ #end
+ ],
+ "vulnerableSoftware": [
+ #foreach($vs in $vuln.getVulnerableSoftware())
+ #if($foreach.count > 1),#end {
+ #if($vs.hasPreviousVersion()) "allPreviousVersion": "true"#end,
+ "name": "$enc.json($vs.name)"
+ }
+ #end
+ ]
+ }
+ #end
+ }
+ #end
+
+ }
+ #end
+ ]
+ }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 8698773ce..36ba0daf3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -597,6 +597,11 @@ Copyright (c) 2012 - Jeremy Long
annotations
3.0.1u2
+
+ com.google.code.gson
+ gson
+ 2.3.1
+
com.h2database
h2