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 7cb0cee7f..47db9f89c 100644 --- a/dependency-check-core/pom.xml +++ b/dependency-check-core/pom.xml @@ -253,6 +253,10 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. com.sun.mail mailapi + + com.google.code.gson + gson + org.apache.maven.scm @@ -383,6 +387,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved. test true + @@ -571,13 +576,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 539cbbc85..340166b0e 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. @@ -186,6 +187,9 @@ public class ReportGenerator { if (format == Format.VULN || format == Format.ALL) { generateReport("VulnerabilityReport", outputStream); } + if (format == Format.JSON || format == Format.ALL) { + generateReport("JsonReport", outputStream); + } } /** @@ -200,12 +204,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"); } + } /** @@ -220,7 +239,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); @@ -242,6 +261,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..4fbf0b79a --- /dev/null +++ b/dependency-check-core/src/main/resources/templates/JsonReport.vsl @@ -0,0 +1,198 @@ +{ + "reportSchema": "1.0", + "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)", + #if($groupID)"groupID":"$enc.json($groupID)",#end + #if($artifactID)"artifactID":"$enc.json($artifactID)",#end + #if($version)"version":"$enc.json($version)",#end + "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 + ] + } +} diff --git a/pom.xml b/pom.xml index 448a901c2..370e4c9fd 100644 --- a/pom.xml +++ b/pom.xml @@ -644,6 +644,11 @@ Copyright (c) 2012 - Jeremy Long annotations 3.0.1u2 + + com.google.code.gson + gson + 2.3.1 + com.h2database h2