diff --git a/buildSrc/src/main/groovy/vuln.tools.java-common-conventions.gradle b/buildSrc/src/main/groovy/vuln.tools.java-common-conventions.gradle index 0a93614a..cc220c8a 100644 --- a/buildSrc/src/main/groovy/vuln.tools.java-common-conventions.gradle +++ b/buildSrc/src/main/groovy/vuln.tools.java-common-conventions.gradle @@ -12,7 +12,7 @@ plugins { } group 'io.github.jeremylong' -version = '5.0.3' +version = '5.1.0' repositories { mavenCentral() diff --git a/open-vulnerability-clients/README.md b/open-vulnerability-clients/README.md index f60f0766..66805180 100644 --- a/open-vulnerability-clients/README.md +++ b/open-vulnerability-clients/README.md @@ -39,14 +39,14 @@ See API usage examples in the [open-vulnerability-store](https://github.com/jere io.github.jeremylong open-vulnerability-clients - 5.0.3 + 5.1.0 ``` ### gradle ```groovy -implementation 'io.github.jeremylong:open-vulnerability-clients:5.0.3' +implementation 'io.github.jeremylong:open-vulnerability-clients:5.1.0' ``` ### api usage diff --git a/vulnz/README.md b/vulnz/README.md index 3351d245..c6aec104 100644 --- a/vulnz/README.md +++ b/vulnz/README.md @@ -72,7 +72,7 @@ export JAVA_OPTS="-Xmx2g" Alternatively, run the CLI using the `-Xmx2g` argument: ```bash -java -Xmx2g -jar ./vulnz-5.0.3.jar +java -Xmx2g -jar ./vulnz-5.1.0.jar ``` ### Creating the Cache @@ -89,7 +89,7 @@ for file in *.json; do gzip -k "${file}"; done Alternatively, without using the above install command: ```bash -./vulnz-5.0.3.jar cve --cache --directory ./cache +./vulnz-5.1.0.jar cve --cache --directory ./cache cd cache for file in *.json; do gzip -k "${file}"; done ``` diff --git a/vulnz/build.gradle b/vulnz/build.gradle index d229c47a..996d696f 100644 --- a/vulnz/build.gradle +++ b/vulnz/build.gradle @@ -16,6 +16,7 @@ dependencies { implementation project(':open-vulnerability-store') implementation 'info.picocli:picocli-spring-boot-starter:4.7.4' implementation 'com.diogonunes:JColor:5.5.1' + implementation 'commons-io:commons-io:2.15.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0' diff --git a/vulnz/src/main/java/io/github/jeremylong/vulnz/cli/commands/CveCommand.java b/vulnz/src/main/java/io/github/jeremylong/vulnz/cli/commands/CveCommand.java index 5ca29616..45dc3872 100644 --- a/vulnz/src/main/java/io/github/jeremylong/vulnz/cli/commands/CveCommand.java +++ b/vulnz/src/main/java/io/github/jeremylong/vulnz/cli/commands/CveCommand.java @@ -32,6 +32,7 @@ import io.github.jeremylong.vulnz.cli.cache.CacheException; import io.github.jeremylong.vulnz.cli.cache.CacheProperties; import io.github.jeremylong.vulnz.cli.model.BasicOutput; +import org.apache.commons.io.output.CountingOutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -41,9 +42,14 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.time.Year; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; @@ -63,6 +69,10 @@ public class CveCommand extends AbstractNvdCommand { * Reference to the logger. */ private static final Logger LOG = LoggerFactory.getLogger(CveCommand.class); + /** + * Hex code characters used in getHex. + */ + private static final String HEXES = "0123456789abcdef"; @CommandLine.ArgGroup(exclusive = true) ConfigGroup configGroup; @@ -213,7 +223,7 @@ public Integer timedCall() throws Exception { properties.save(); return status; } catch (CacheException ex) { - LOG.error(ex.getMessage()); + LOG.error(ex.getMessage(), ex); } return 1; } @@ -274,6 +284,7 @@ private Integer processRequest(NvdCveClientBuilder builder, CacheProperties prop for (Map.Entry> entry : cves.entrySet()) { File file = new File(properties.getDirectory(), prefix + entry.getKey() + ".json.gz"); + File meta = new File(properties.getDirectory(), prefix + entry.getKey() + ".meta"); List vulnerabilities = new ArrayList(entry.getValue().values()); vulnerabilities.sort((v1, v2) -> { return v1.getCve().getId().compareTo(v2.getCve().getId()); @@ -291,16 +302,62 @@ private Integer processRequest(NvdCveClientBuilder builder, CacheProperties prop properties.set("lastModifiedDate." + entry.getKey(), timestamp); CveApiJson20 data = new CveApiJson20(vulnerabilities.size(), 0, vulnerabilities.size(), format, version, timestamp, vulnerabilities); + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new CacheException("Unable to calculate sha256 checksum", e); + } + long byteCount = 0; try (FileOutputStream fileOutputStream = new FileOutputStream(file); - GZIPOutputStream gzipOutputStream = new GZIPOutputStream(fileOutputStream);) { - objectMapper.writeValue(gzipOutputStream, data); + GZIPOutputStream gzipOutputStream = new GZIPOutputStream(fileOutputStream); + DigestOutputStream digestOutputStream = new DigestOutputStream(gzipOutputStream, md); + CountingOutputStream countingOutputStream = new CountingOutputStream(digestOutputStream)) { + objectMapper.writeValue(countingOutputStream, data); + byteCount = countingOutputStream.getByteCount(); } catch (IOException ex) { throw new CacheException("Unable to write cached data: " + file, ex); } + String checksum = getHex(md.digest()); + try (FileOutputStream fileOutputStream = new FileOutputStream(meta); + OutputStreamWriter osw = new OutputStreamWriter(fileOutputStream, "UTF-8"); + PrintWriter writer = new PrintWriter(osw)) { + final String lmd = DateTimeFormatter.ISO_DATE_TIME.format(timestamp); + writer.println("lastModifiedDate:" + lmd); + writer.println("size:" + byteCount); + writer.println("gzSize:" + file.length()); + writer.println("sha256:" + checksum); + } catch (IOException ex) { + throw new CacheException("Unable to write cached meta-data: " + file, ex); + } } return 0; } + /** + *

+ * Converts a byte array into a hex string. + *

+ * + *

+ * This method was copied from + * http://www.rgagnon.com/javadetails/java-0596.html + *

+ * + * @param raw a byte array + * @return the hex representation of the byte array + */ + public static String getHex(byte[] raw) { + if (raw == null) { + return null; + } + final StringBuilder hex = new StringBuilder(2 * raw.length); + for (final byte b : raw) { + hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt(b & 0x0F)); + } + return hex.toString(); + } + private void collectCves(HashMap> cves, Collection vulnerabilities) { for (DefCveItem item : vulnerabilities) {