From ae0558b3e87f6e6183780c71538553cac6dee6ef Mon Sep 17 00:00:00 2001 From: "CORP\\mmrzik" Date: Fri, 24 Nov 2023 21:11:26 +0100 Subject: [PATCH] #103: writing security json file --- .../tools/ide/tool/LocalToolCommandlet.java | 4 + .../tools/ide/tool/ToolCommandlet.java | 42 ++-- .../ide/url/model/file/SecurityEntry.java | 17 -- .../model/file/json/UrlSecurityJsonFile.java | 181 ++++++++++++++++ .../ide/url/model/folder/UrlEdition.java | 12 + .../ide/url/updater/AbstractUrlUpdater.java | 9 +- .../tools/ide/url/updater/UpdateManager.java | 6 + .../tools/ide/version/VersionRange.java | 34 ++- .../tools/ide/version/VersionRangeTest.java | 28 +++ .../java/com/devonfw/tools/security/Main.java | 205 +++++++++++------- .../devonfw/tools/security/UrlAnalyzer.java | 23 +- 11 files changed, 440 insertions(+), 121 deletions(-) delete mode 100644 cli/src/main/java/com/devonfw/tools/ide/url/model/file/SecurityEntry.java create mode 100644 cli/src/main/java/com/devonfw/tools/ide/url/model/file/json/UrlSecurityJsonFile.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index 8505dead7..36e6c8566 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -63,6 +63,10 @@ protected boolean doInstall(boolean silent) { // install configured version of our tool in the software repository if not already installed ToolInstallation installation = installInRepo(configuredVersion); + VersionIdentifier selectedVersion = securityRiskInteraction(configuredVersion); + + System.out.println("Selected version: " + selectedVersion); + // check if we already have this version installed (linked) locally in IDE_HOME/software VersionIdentifier installedVersion = getInstalledVersion(); VersionIdentifier resolvedVersion = installation.resolvedVersion(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java index cd7636099..abb5b1028 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java @@ -16,6 +16,7 @@ import com.devonfw.tools.ide.environment.EnvironmentVariablesType; import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.io.TarCompression; +import com.devonfw.tools.ide.json.mapping.JsonMapping; import com.devonfw.tools.ide.os.MacOsHelper; import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; @@ -23,6 +24,7 @@ import com.devonfw.tools.ide.url.model.file.UrlSecurityFile; import com.devonfw.tools.ide.util.FilenameUtil; import com.devonfw.tools.ide.version.VersionIdentifier; +import com.fasterxml.jackson.databind.ObjectMapper; /** * {@link Commandlet} for a tool integrated into the IDE. @@ -172,7 +174,8 @@ public boolean install(boolean silent) { return doInstall(silent); } - protected String question(String question, String ... options) { + protected String question(String question, String... options) { + question += " Do you want to"; for (int i = 0; i < options.length - 1; i++) { options[i] += " or"; @@ -180,16 +183,19 @@ protected String question(String question, String ... options) { options[options.length - 1] += "?"; return this.context.question(question, options); } + protected VersionIdentifier securityRiskInteraction(VersionIdentifier configuredVersion) { // TODO vielleicht security file auch neu als json file wenn 1.2 > 2.9 nicht ausreicht - // TODO vielleicht auch zusätzlich das tool scannen sobald es installiert ist, - // TODO webpage:\nhttps://github.com/devonfw/ide/blob/master/documentation/vulnerabilities.asciidoc\n\n"; + // TODO if no version is save, find a version that has lowest security risk, or suggest multiple ones, such that the + // user can choose + UrlSecurityFile securityFile = this.context.getUrls().getEdition(this.tool, this.getEdition()).getSecurityFile(); - VersionIdentifier current = this.context.getUrls().getVersion(this.tool, this.getEdition(), - configuredVersion); + ObjectMapper mapper = JsonMapping.create(); + + VersionIdentifier current = this.context.getUrls().getVersion(this.tool, this.getEdition(), configuredVersion); if (!securityFile.contains(current)) { return configuredVersion; } @@ -201,7 +207,8 @@ protected VersionIdentifier securityRiskInteraction(VersionIdentifier configured int currentVersionIndex = allVersions.indexOf(current); - // VersionIdentifier nextVersion = currentVersionIndex == 0 ? null : allVersions.get(allVersions.indexOf(currentVersion) - 1); + // VersionIdentifier nextVersion = currentVersionIndex == 0 ? null : + // allVersions.get(allVersions.indexOf(currentVersion) - 1); VersionIdentifier nextSafe = null; for (int i = currentVersionIndex - 1; i >= 0; i--) { if (!securityFile.contains(allVersions.get(i))) { @@ -225,11 +232,12 @@ protected VersionIdentifier securityRiskInteraction(VersionIdentifier configured } String currentIsUnsafe = "Currently, version " + current + " of " + this.getName() + " is installed, " - + "which is has a vulnerability."; + + "which is has a vulnerability:\n" + " TODOODODO" + "\n\n"; + String stay = "stay with the current unsafe version (" + current + ")"; String installLatestSafe = "install the latest safe version (" + latestSafe + ")"; String installSafeLatest = "install the (safe) latest version (" + latestSafe + ")"; - String installNextSafe = "install the next safe version (" + nextSafe+ ")"; + String installNextSafe = "install the next safe version (" + nextSafe + ")"; if (current.equals(latest)) { String answer = question(currentIsUnsafe, stay, installLatestSafe); @@ -237,25 +245,23 @@ protected VersionIdentifier securityRiskInteraction(VersionIdentifier configured } else if (nextSafe == null) { // TODO also allow selection of next or previous version, even if they are unsafe? - String answer = question(currentIsUnsafe + " All newer versions are also not safe.", - stay, installLatestSafe); + String answer = question(currentIsUnsafe + " All newer versions are also not safe.", stay, installLatestSafe); return answer.startsWith(stay) ? current : latestSafe; } else if (nextSafe.equals(latest)) { - String answer = question( currentIsUnsafe + " Of the newer versions, only the latest is safe.", - stay, installSafeLatest); + String answer = question(currentIsUnsafe + " Of the newer versions, only the latest is safe.", stay, + installSafeLatest); return answer.startsWith(stay) ? current : latestSafe; } else if (nextSafe.equals(latestSafe)) { - String answer = question(currentIsUnsafe +" Of the newer versions, only the version " - + nextSafe + " is safe.", stay, "Install the safe version (" + nextSafe + ")"); + String answer = question(currentIsUnsafe + " Of the newer versions, only the version " + nextSafe + " is safe.", + stay, "Install the safe version (" + nextSafe + ")"); return answer.startsWith(stay) ? current : nextSafe; } else { if (latest.equals(latestSafe)) { String answer = question(currentIsUnsafe, stay, installNextSafe, installSafeLatest); - return answer.startsWith(stay) ? current - : answer.startsWith(installNextSafe) ? nextSafe : latestSafe; + return answer.startsWith(stay) ? current : answer.startsWith(installNextSafe) ? nextSafe : latestSafe; } else { String answer = question(currentIsUnsafe, stay, installNextSafe, installLatestSafe); @@ -263,8 +269,8 @@ protected VersionIdentifier securityRiskInteraction(VersionIdentifier configured } } - // VersionIdentifier chosenVersion = securityRiskInteraction(configuredVersion); - // setVersion(chosenVersion, silent); + // VersionIdentifier chosenVersion = securityRiskInteraction(configuredVersion); + // setVersion(chosenVersion, silent); } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/url/model/file/SecurityEntry.java b/cli/src/main/java/com/devonfw/tools/ide/url/model/file/SecurityEntry.java deleted file mode 100644 index dba1847dc..000000000 --- a/cli/src/main/java/com/devonfw/tools/ide/url/model/file/SecurityEntry.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.devonfw.tools.ide.url.model.file; - -import com.devonfw.tools.ide.version.VersionRange; - -/** - * A simple container with the information about a security entry. - * - * @param versionRange The version range of affected versions. - * @param severity The severity of the security issue (0.0 - 10.0). - * @param severityVersion The version of the severity. As of November 2023 its either v2 or v3. - * @param cveName The CVE name. - * @param Description The description of the security issue. - * @param url The url to the security issue. - */ -public record SecurityEntry(VersionRange versionRange, double severity, String severityVersion, String cveName, - String Description, String url) { -} diff --git a/cli/src/main/java/com/devonfw/tools/ide/url/model/file/json/UrlSecurityJsonFile.java b/cli/src/main/java/com/devonfw/tools/ide/url/model/file/json/UrlSecurityJsonFile.java new file mode 100644 index 000000000..d7305ca4a --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/url/model/file/json/UrlSecurityJsonFile.java @@ -0,0 +1,181 @@ +package com.devonfw.tools.ide.url.model.file.json; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.devonfw.tools.ide.json.mapping.JsonMapping; +import com.devonfw.tools.ide.url.model.file.AbstractUrlFile; +import com.devonfw.tools.ide.url.model.folder.UrlEdition; +import com.devonfw.tools.ide.version.VersionIdentifier; +import com.devonfw.tools.ide.version.VersionRange; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class UrlSecurityJsonFile extends AbstractUrlFile { + + /** {@link #getName() Name} of security json file. */ + public static final String FILENAME_SECURITY = "security.json"; + + private static final Logger LOG = LoggerFactory.getLogger(UrlSecurityJsonFile.class); + + List matches; + + /** + * The constructor. + * + * @param parent the {@link #getParent() parent folder}. + */ + public UrlSecurityJsonFile(UrlEdition parent) { + + super(parent, FILENAME_SECURITY); + this.matches = new ArrayList<>(); + } + + public boolean addSecurityMatch(VersionRange versionRange, double severity, String severityVersion, String cveName, + String description, String nistUrl, List referenceUrl) { + + UrlSecurityWarning newWarning = new UrlSecurityWarning(severity, severityVersion, cveName, description, nistUrl, + referenceUrl); + for (UrlSecurityMatch match : matches) { + if (match.getVersionRange().equals(versionRange)) { + boolean added = match.addWarning(newWarning); + this.modified = this.modified || added; + return added; + } + } + UrlSecurityMatch newMatch = new UrlSecurityMatch(versionRange); + newMatch.addWarning(newWarning); + this.modified = true; + return matches.add(newMatch); + } + + public boolean removeSecurityMatch(VersionRange versionRange) { + + for (UrlSecurityMatch match : matches) { + if (match.getVersionRange().equals(versionRange)) { + boolean removed = matches.remove(match); + this.modified = this.modified || removed; + return removed; + } + } + return false; + } + + public boolean contains(VersionIdentifier version) { + + for (UrlSecurityMatch match : matches) { + if (match.getVersionRange().contains(version)) { + return true; + } + } + return false; + } + + public Set getSecurityWarnings(VersionIdentifier version) { + + Set warnings = new HashSet<>(); + for (UrlSecurityMatch match : matches) { + if (match.getVersionRange().contains(version)) { + warnings.addAll(match.getWarnings()); + } + } + return warnings; + } + + public void clearSecurityMatches() { + + this.matches.clear(); + } + + @Override + protected void doLoad() { + + if (!Files.exists(getPath())) { + return; + } + ObjectMapper mapper = JsonMapping.create(); + try { + matches = mapper.readValue(getPath().toFile(), new TypeReference>() { + }); + } catch (IOException e) { + throw new IllegalStateException("The UrlSecurityJsonFile " + getPath() + " could not be parsed.", e); + } + } + + @Override + protected void doSave() { + + Path path = getPath(); + ObjectMapper mapper = JsonMapping.create(); + + if (this.matches.isEmpty() && !Files.exists(path)) { + return; + } + + String jsonString; + try { + jsonString = mapper.writeValueAsString(matches); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + try (BufferedWriter bw = Files.newBufferedWriter(path, StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.WRITE, StandardOpenOption.CREATE)) { + bw.write(jsonString + "\n"); + } catch (IOException e) { + throw new IllegalStateException("Failed to save file " + path, e); + } + } +} + +class UrlSecurityMatch { + private final VersionRange versionRange; + + private final Set warnings; + + public UrlSecurityMatch() { + + // this constructor is needed for jackson deserialization + this.versionRange = null; + this.warnings = new HashSet<>(); + } + + public UrlSecurityMatch(VersionRange versionRange) { + + this.versionRange = versionRange; + this.warnings = new HashSet<>(); + } + + public VersionRange getVersionRange() { + + return versionRange; + } + + public Set getWarnings() { + + return warnings; + } + + public boolean addWarning(UrlSecurityWarning warning) { + + return this.warnings.add(warning); + } + +} + +// severity could be java.math.BigDecimal; instead of double (unsing BigDecimal("123.4").setScale(1, +// BigDecimal.ROUND_HALF_UP);) +record UrlSecurityWarning(double severity, String severityVersion, String cveName, String description, String nistUrl, + List referenceUrl) { +}; \ No newline at end of file diff --git a/cli/src/main/java/com/devonfw/tools/ide/url/model/folder/UrlEdition.java b/cli/src/main/java/com/devonfw/tools/ide/url/model/folder/UrlEdition.java index a9390e232..b3f0fdea3 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/url/model/folder/UrlEdition.java +++ b/cli/src/main/java/com/devonfw/tools/ide/url/model/folder/UrlEdition.java @@ -2,6 +2,7 @@ import com.devonfw.tools.ide.url.model.AbstractUrlFolderWithParent; import com.devonfw.tools.ide.url.model.file.UrlSecurityFile; +import com.devonfw.tools.ide.url.model.file.json.UrlSecurityJsonFile; /** * An {@link UrlFolder} representing the actual edition of a {@link UrlTool}. The default edition may have the same @@ -12,6 +13,8 @@ public class UrlEdition extends AbstractUrlFolderWithParent private UrlSecurityFile securityFile; + private UrlSecurityJsonFile securityJsonFile; + /** * The constructor. * @@ -48,6 +51,15 @@ public UrlSecurityFile getSecurityFile() { return this.securityFile; } + public UrlSecurityJsonFile getSecurityJsonFile() { + + if (this.securityJsonFile == null) { + this.securityJsonFile = new UrlSecurityJsonFile(this); + this.securityJsonFile.load(false); + } + return this.securityJsonFile; + } + @Override public void save() { diff --git a/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java index ded51f75c..7874bc47f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java @@ -99,12 +99,17 @@ protected final String getToolWithEdition() { protected String getCpeVendor() { - return ""; + return null; } protected String getCpeProduct() { - return ""; + return null; + } + + protected String getCpeEdition() { + + return null; } protected String mapUrlVersionToCpeVersion(String version) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java b/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java index 70788f5e9..708e63304 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java +++ b/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java @@ -108,6 +108,12 @@ public String getCpeProduct(String tool) { .map(AbstractUrlUpdater::getCpeProduct).orElse(null); } + public String getCpeEdition(String tool) { + + return updaters.stream().filter(updater -> updater.getTool().equals(tool)).findFirst() + .map(AbstractUrlUpdater::getCpeEdition).orElse(null); + } + public String mapUrlVersionToCpeVersion(String tool, String urlVersion) { return updaters.stream().filter(updater -> updater.getTool().equals(tool)).findFirst() diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java b/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java index d09021a2b..656117531 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java +++ b/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java @@ -1,5 +1,8 @@ package com.devonfw.tools.ide.version; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + /** * Container for a range of versions. */ @@ -13,8 +16,9 @@ public final class VersionRange implements Comparable { * The constructor. * * @param min the {@link #getMin() minimum}. - * @param max the {@link #getMax() maximum}. + * @param max the {@link #getMax() maximum} (including). */ + public VersionRange(VersionIdentifier min, VersionIdentifier max) { super(); @@ -25,6 +29,7 @@ public VersionRange(VersionIdentifier min, VersionIdentifier max) { /** * @return the minimum {@link VersionIdentifier} or {@code null} for no lower bound. */ + // @JsonBackReference public VersionIdentifier getMin() { return this.min; @@ -33,6 +38,7 @@ public VersionIdentifier getMin() { /** * @return the maximum {@link VersionIdentifier} or {@code null} for no upper bound. */ + // @JsonBackReference public VersionIdentifier getMax() { return this.max; @@ -73,6 +79,31 @@ public int compareTo(VersionRange o) { } @Override + public boolean equals(Object obj) { + + if (this == obj) + return true; + + if (obj == null || getClass() != obj.getClass()) + return false; + + VersionRange o = (VersionRange) obj; + + if (this.min == null && this.max == null) { + return o.min == null && o.max == null; + } + if (this.min == null) { + return o.min == null && this.max.equals(o.max); + } + if (this.max == null) { + return this.min.equals(o.min) && o.max == null; + } + return this.min.equals(o.min) && this.max.equals(o.max); + + } + + @Override + @JsonValue public String toString() { StringBuilder sb = new StringBuilder(); @@ -90,6 +121,7 @@ public String toString() { * @param value the {@link #toString() string representation} of a {@link VersionRange} to parse. * @return the parsed {@link VersionRange}. */ + @JsonCreator public static VersionRange of(String value) { int index = value.indexOf('>'); diff --git a/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java b/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java new file mode 100644 index 000000000..83f9209b8 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java @@ -0,0 +1,28 @@ +package com.devonfw.tools.ide.version; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +public class VersionRangeTest extends Assertions { + @Test + void testEquals() { + + assertThat(VersionRange.of("1.2>")).isEqualTo(VersionRange.of("1.2>")); + assertThat(VersionRange.of("1.2>3")).isEqualTo(VersionRange.of("1.2>3")); + assertThat(VersionRange.of(">3")).isEqualTo(VersionRange.of(">3")); + assertThat(VersionRange.of(">")).isEqualTo(VersionRange.of(">")); + assertThat(VersionRange.of("8u302b08>11.0.14_9")).isEqualTo(VersionRange.of("8u302b08>11.0.14_9")); + + assertThat(VersionRange.of("1>")).isNotEqualTo(null); + assertThat(VersionRange.of("1.2>")).isNotEqualTo(VersionRange.of("1>")); + assertThat(VersionRange.of("1.2>3")).isNotEqualTo(VersionRange.of("1.2>")); + assertThat(VersionRange.of("1.2>3")).isNotEqualTo(VersionRange.of(">3")); + assertThat(VersionRange.of("1.2>")).isNotEqualTo(VersionRange.of("1.2>3")); + assertThat(VersionRange.of(">3")).isNotEqualTo(VersionRange.of("1.2>3")); + assertThat(VersionRange.of(">3")).isNotEqualTo(VersionRange.of(">")); + assertThat(VersionRange.of(">")).isNotEqualTo(VersionRange.of(">3")); + assertThat(VersionRange.of("8u302b08>11.0.14_9")).isNotEqualTo(VersionRange.of("8u302b08>11.0.15_9")); + assertThat(VersionRange.of("8u302b08>11.0.14_9")).isNotEqualTo(VersionRange.of("8u302b08>11.0.14_0")); + + } +} diff --git a/security/src/main/java/com/devonfw/tools/security/Main.java b/security/src/main/java/com/devonfw/tools/security/Main.java index 22c6daaa9..d78fff2e5 100644 --- a/security/src/main/java/com/devonfw/tools/security/Main.java +++ b/security/src/main/java/com/devonfw/tools/security/Main.java @@ -4,122 +4,170 @@ import java.nio.file.Paths; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; -import com.devonfw.tools.ide.context.IdeContext; -import com.devonfw.tools.ide.context.IdeContextConsole; -import com.devonfw.tools.ide.json.mapping.JsonMapping; -import com.devonfw.tools.ide.log.IdeLogLevel; -import com.devonfw.tools.ide.url.model.file.SecurityEntry; -import com.devonfw.tools.ide.url.updater.UpdateManager; -import com.devonfw.tools.ide.version.VersionIdentifier; -import com.devonfw.tools.ide.version.VersionRange; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.AnalysisPhase; import org.owasp.dependencycheck.analyzer.FileNameAnalyzer; import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer; -import org.owasp.dependencycheck.dependency.*; +import org.owasp.dependencycheck.dependency.Dependency; +import org.owasp.dependencycheck.dependency.Reference; +import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.dependency.VulnerableSoftware; import org.owasp.dependencycheck.exception.ExceptionCollection; import org.owasp.dependencycheck.utils.Settings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.context.IdeContextConsole; +import com.devonfw.tools.ide.log.IdeLogLevel; +import com.devonfw.tools.ide.url.model.file.json.UrlSecurityJsonFile; +import com.devonfw.tools.ide.url.updater.UpdateManager; +import com.devonfw.tools.ide.version.VersionIdentifier; +import com.devonfw.tools.ide.version.VersionRange; public class Main { + + private static final Logger logger = LoggerFactory.getLogger(Main.class); + + private static final String CVE_BASE_URL = "https://nvd.nist.gov/vuln/detail/"; + + private static double minV2Severity; + + private static double minV3Severity; + public static void main(String[] args) { - // TODO edit dependency check properties file to switch off analysers, this file is currently read only - // TODO maybe this can be done in pom.xml - // or simply remove it like FileNameAnalyzer was removed - // TODO: note settings.setBoolean(Settings.KEYS.ANALYZER_NODE_AUDIT_USE_CACHE, false); + if (args.length != 2) { + throw new RuntimeException("Please provide 2 numbers: minV2Severity and minV3Severity"); + } + try { + minV2Severity = Double.parseDouble(args[0]); + minV3Severity = Double.parseDouble(args[1]); + } catch (NumberFormatException e) { + throw new RuntimeException("These two args could not be parsed as double"); + } + run(minV2Severity, minV3Severity); - // TODO ~/.m2/repository/org/owasp/dependency-check-utils/8.4.2/data/7.0/odc.update.lock - // why is this not in projects dir but in user dir? + } - Settings settings = new Settings(); - Engine engine = new Engine(settings); // doesn't work with "try with resource" + private static void run(double minV2Severity, double minV3Severity) { IdeContext ideContext = new IdeContextConsole(IdeLogLevel.INFO, null, false); - UpdateManager updateManager = new UpdateManager(ideContext.getUrlsPath(), null); - FileTypeAnalyzer myAnalyzer = new UrlAnalyzer(updateManager); - engine.getFileTypeAnalyzers().add(myAnalyzer); - engine.getAnalyzers(AnalysisPhase.INFORMATION_COLLECTION).add(myAnalyzer); - engine.getAnalyzers(AnalysisPhase.INFORMATION_COLLECTION) - .removeIf(analyzer -> analyzer instanceof FileNameAnalyzer); + // TODO edit dependency check properties file to switch off analysers, this file is currently read only + // TODO maybe this can be done in pom.xml + // or simply remove it like FileNameAnalyzer was removed - engine.scan("C:\\projects\\_ide\\myUrls"); + // note: settings.setBoolean(Settings.KEYS.ANALYZER_NODE_AUDIT_USE_CACHE, false); - try { - engine.analyzeDependencies();// needed for db stuff which is private - } catch (ExceptionCollection e) { - throw new RuntimeException(e); - } - float minV2Severity = 0.0f; - float minV3Severity = 0.0f; + // TODO ~/.m2/repository/org/owasp/dependency-check-utils/8.4.2/data/7.0/odc.update.lock + // why is this not in projects dir but in user dir? - for (Dependency dependency : engine.getDependencies()) { - Set vulnerabilities = dependency.getVulnerabilities(true); + Dependency[] dependencies = getDependenciesWithVulnerabilities(ideContext); + + for (Dependency dependency : dependencies) { String filePath = dependency.getFilePath(); Path parent = Paths.get(filePath).getParent(); String tool = parent.getParent().getParent().getFileName().toString(); String edition = parent.getParent().getFileName().toString(); - List sortedVersions = ideContext.getUrls().getSortedVersions(tool, edition); - // TODO read min levels from console or args[] - // TODO list all vulnerabilities, so maybe description, all fields of cvssv3 and cvssv2, cve name, source, - // url of vulnerabilityIds, and vulnerableSoftware - // TODO take all vulnerabilities, or ask for another min level und update the numbers of vulnerabilities - // TODO write vulnerabilities to file -> new format? that includes CVE name? + UrlSecurityJsonFile securityFile = ideContext.getUrls().getEdition(tool, edition).getSecurityJsonFile(); + securityFile.clearSecurityMatches(); - // List foundVulnerabilities = new ArrayList<>(); + List sortedVersions = ideContext.getUrls().getSortedVersions(tool, edition); + Set vulnerabilities = dependency.getVulnerabilities(true); for (Vulnerability vulnerability : vulnerabilities) { + addVulnerabilityToSecurityFile(vulnerability, securityFile, sortedVersions); + } + securityFile.save(); + } + } - if (vulnerability.getCvssV2() == null && vulnerability.getCvssV3() == null) { - throw new RuntimeException("Vulnerability without severity found: " + vulnerability.getName()); - } - boolean hasV3Severity = vulnerability.getCvssV3() != null; - double severity = hasV3Severity - ? vulnerability.getCvssV3().getBaseScore() - : vulnerability.getCvssV2().getScore(); - String severityVersion = hasV3Severity ? "v3" : "v2"; - String cveName = vulnerability.getName(); - String description = vulnerability.getDescription(); - - boolean toLowSeverity = hasV3Severity ? severity < minV3Severity : severity < minV2Severity; - if (toLowSeverity) { - continue; - } + private static void addVulnerabilityToSecurityFile(Vulnerability vulnerability, UrlSecurityJsonFile securityFile, + List sortedVersions) { - VersionRange versionRange = getVersionRangeFromVulnerability(sortedVersions, vulnerability); - SecurityEntry securityEntry = new SecurityEntry(versionRange, severity, severityVersion, cveName, description, - null); - ObjectMapper mapper = JsonMapping.create(); - try { - String jsonString = mapper.writeValueAsString(securityEntry); - System.out.println(jsonString); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } + if (vulnerability.getCvssV2() == null && vulnerability.getCvssV3() == null) { + throw new RuntimeException("Vulnerability without severity found: " + vulnerability.getName()); } - engine.close(); + boolean hasV3Severity = vulnerability.getCvssV3() != null; + double severity = hasV3Severity ? vulnerability.getCvssV3().getBaseScore() : vulnerability.getCvssV2().getScore(); + String severityVersion = hasV3Severity ? "v3" : "v2"; + String cveName = vulnerability.getName(); + String description = vulnerability.getDescription(); + String nistUrl = CVE_BASE_URL + cveName; + List referenceUrls = vulnerability.getReferences().stream().map(Reference::getUrl) + .collect(Collectors.toList()); + if (referenceUrls.isEmpty()) { + referenceUrls.add("No references found, try searching for the CVE name (" + cveName + ") on the web."); + } + boolean toLowSeverity = hasV3Severity ? severity < minV3Severity : severity < minV2Severity; + if (toLowSeverity) { + return; + } + VersionRange versionRange = getVersionRangeFromVulnerability(sortedVersions, vulnerability); + if (versionRange == null) { + logger.info( + "Vulnerability {} is not relevant because its affected versions have no overlap with the versions available " + + "through IDEasy.", + vulnerability.getName()); + return; + } + + securityFile.addSecurityMatch(versionRange, severity, severityVersion, cveName, description, nistUrl, + referenceUrls); + } - static VersionRange getVersionRangeFromInterval(List sortedVersions, String vStartExcluding, String vStartIncluding, - String vEndIncluding, String vEndExcluding) { + private static Dependency[] getDependenciesWithVulnerabilities(IdeContext ideContext) { + + Settings settings = new Settings(); + // Using try with resource or engine.close at the end resulted in SEVERE warning by owasp + Engine engine = new Engine(settings); + UpdateManager updateManager = new UpdateManager(ideContext.getUrlsPath(), null); + FileTypeAnalyzer myAnalyzer = new UrlAnalyzer(updateManager); + engine.getFileTypeAnalyzers().add(myAnalyzer); + engine.getAnalyzers(AnalysisPhase.INFORMATION_COLLECTION).add(myAnalyzer); + engine.getAnalyzers(AnalysisPhase.INFORMATION_COLLECTION).removeIf(analyze -> analyze instanceof FileNameAnalyzer); + + // engine.scan(ideContext.getUrlsPath().toString()); + engine.scan("C:\\projects\\_ide\\myUrls"); + + try { + engine.analyzeDependencies(); + } catch (ExceptionCollection e) { + throw new RuntimeException(e); + } + return engine.getDependencies(); + } + + static VersionRange getVersionRangeFromInterval(List sortedVersions, String vStartExcluding, + String vStartIncluding, String vEndIncluding, String vEndExcluding) { VersionIdentifier max = null; if (vEndIncluding != null) { - max = VersionIdentifier.of(vEndIncluding); // this allows that max is not part of the available versions, this has no impact on the contains method but maybe confusing + max = VersionIdentifier.of(vEndIncluding); // this allows that max is not part of the available versions, this has + // no impact on the contains method but maybe confusing } else if (vEndExcluding != null) { VersionIdentifier end = VersionIdentifier.of(vEndExcluding); for (VersionIdentifier version : sortedVersions) { if (version.isLess(end)) { + + // TODO here the version from the name in url dir is v.2.7.0 for example and end is 2.7.2 which should be + // smaller but is not + // sinvce the v is there, i either have to map the sorted versions and remove the v or add the v to "end" max = version; break; } } + if (max == null) { // vEndExcluding is smaller or equal than all available versions -> this vulnerability is not + // relevant and just leaving max to be null could result in a version range like ">" meaning all versions are + // effected, which is wrong. + return null; + } } VersionIdentifier min = null; @@ -133,6 +181,11 @@ static VersionRange getVersionRangeFromInterval(List sortedVe break; } } + if (min == null) { // vStartExcluding is greater or equal than all available versions -> this vulnerability is not + // relevant and just leaving min to be null could result in a version range like ">" meaning all versions are + // effected, which is wrong. + return null; + } } return new VersionRange(min, max); } @@ -147,11 +200,11 @@ static VersionRange getVersionRangeFromVulnerability(List sor String vStartIncluding = matchedVulnerableSoftware.getVersionStartIncluding(); if (vEndExcluding == null && vEndIncluding == null && vStartExcluding == null && vStartIncluding == null) { - throw new RuntimeException("Vulnerability without version range found: " + vulnerability.getName()); + // maybe instead all versions are vulnerable in this case + return VersionRange.of(">"); + // throw new RuntimeException("Vulnerability without version range found: " + vulnerability.getName()); } - return getVersionRangeFromInterval(sortedVersions, vStartExcluding, vStartIncluding, vEndIncluding, - vEndExcluding); + return getVersionRangeFromInterval(sortedVersions, vStartExcluding, vStartIncluding, vEndIncluding, vEndExcluding); } } - diff --git a/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java b/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java index dc384d822..a85985846 100644 --- a/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java +++ b/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java @@ -4,8 +4,6 @@ import java.nio.file.Path; import java.nio.file.Paths; -import com.devonfw.tools.ide.context.IdeContextConsole; -import com.devonfw.tools.ide.log.IdeLogLevel; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.AbstractFileTypeAnalyzer; import org.owasp.dependencycheck.analyzer.AnalysisPhase; @@ -16,7 +14,6 @@ import org.owasp.dependencycheck.dependency.EvidenceType; import org.owasp.dependencycheck.exception.InitializationException; -import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.url.updater.UpdateManager; public class UrlAnalyzer extends AbstractFileTypeAnalyzer { @@ -27,6 +24,7 @@ public class UrlAnalyzer extends AbstractFileTypeAnalyzer { private static final String ANALYZER_NAME = "UrlAnalyzer"; private final UpdateManager updateManager; + public UrlAnalyzer(UpdateManager updateManager) { fileFilter = new UrlFileFilter(); @@ -36,8 +34,6 @@ public UrlAnalyzer(UpdateManager updateManager) { @Override protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException { - - String filePath = dependency.getFilePath(); Path parent = Paths.get(filePath).getParent(); String tool = parent.getParent().getParent().getFileName().toString(); @@ -47,17 +43,28 @@ protected void analyzeDependency(Dependency dependency, Engine engine) throws An // adding vendor evidence String vendor = updateManager.getCpeVendor(tool); - Evidence evidence = new Evidence(source, "CpeVendor", vendor, Confidence.HIGH); + Evidence evidence; + if (vendor == null) { + vendor = tool; + } + evidence = new Evidence(source, "CpeVendor", vendor, Confidence.HIGH); dependency.addEvidence(EvidenceType.VENDOR, evidence); // adding product evidence String product = updateManager.getCpeProduct(tool); - if (product == null) { + if (product == null) { // for the product it is reasonable to assume that "tool" is the product in most cases product = tool; } evidence = new Evidence(source, "CpeProduct", product, Confidence.HIGH); dependency.addEvidence(EvidenceType.PRODUCT, evidence); + // adding edition evidence + String editionEvidence = updateManager.getCpeEdition(tool); + if (editionEvidence != null) { + evidence = new Evidence(source, "CpeEdition", editionEvidence, Confidence.HIGH); + dependency.addEvidence(EvidenceType.PRODUCT, evidence); + } + // adding version evidence String version = updateManager.mapUrlVersionToCpeVersion(tool, parent.getFileName().toString()); evidence = new Evidence(source, "CpeVersion", version, Confidence.HIGH); @@ -72,6 +79,7 @@ public boolean isEnabled() { @Override protected String getAnalyzerEnabledSettingKey() { + // whether this Analyzer is enabled or not is not configurable but fixed by isEnabled() return null; } @@ -84,6 +92,7 @@ protected FileFilter getFileFilter() { @Override protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationException { + // nothing to prepare here }