diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java index 29bf2a875..9de608975 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/HintAnalyzer.java @@ -25,6 +25,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.exception.AnalysisException; @@ -36,6 +37,7 @@ import org.owasp.dependencycheck.utils.DownloadFailedException; import org.owasp.dependencycheck.utils.Downloader; import org.owasp.dependencycheck.utils.FileUtils; import org.owasp.dependencycheck.utils.Settings; +import org.owasp.dependencycheck.xml.hints.EvidenceMatcher; import org.owasp.dependencycheck.xml.hints.VendorDuplicatingHintRule; import org.owasp.dependencycheck.xml.hints.HintParseException; import org.owasp.dependencycheck.xml.hints.HintParser; @@ -136,23 +138,23 @@ public class HintAnalyzer extends AbstractAnalyzer { protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException { for (HintRule hint : hints.getHintRules()) { boolean matchFound = false; - for (Evidence given : hint.getGivenVendor()) { - if (dependency.getVendorEvidence().getEvidence().contains(given)) { + for (EvidenceMatcher given : hint.getGivenVendor()) { + if (hasMatchingEvidence(dependency.getVendorEvidence().getEvidence(),given)) { matchFound = true; break; } } if (!matchFound) { - for (Evidence given : hint.getGivenProduct()) { - if (dependency.getProductEvidence().getEvidence().contains(given)) { + for (EvidenceMatcher given : hint.getGivenProduct()) { + if (hasMatchingEvidence(dependency.getProductEvidence().getEvidence(),given)) { matchFound = true; break; } } } if (!matchFound) { - for (Evidence given : hint.getGivenVersion()) { - if (dependency.getVersionEvidence().getEvidence().contains(given)) { + for (EvidenceMatcher given : hint.getGivenVersion()) { + if (hasMatchingEvidence(dependency.getVersionEvidence().getEvidence(),given)) { matchFound = true; break; } @@ -176,20 +178,14 @@ public class HintAnalyzer extends AbstractAnalyzer { for (Evidence e : hint.getAddVersion()) { dependency.getVersionEvidence().addEvidence(e); } - for (Evidence e : hint.getRemoveVendor()) { - if (dependency.getVendorEvidence().getEvidence().contains(e)) { - dependency.getVendorEvidence().getEvidence().remove(e); - } + for (EvidenceMatcher e : hint.getRemoveVendor()) { + removeMatchingEvidences(dependency.getVendorEvidence().getEvidence(),e); } - for (Evidence e : hint.getRemoveProduct()) { - if (dependency.getProductEvidence().getEvidence().contains(e)) { - dependency.getProductEvidence().getEvidence().remove(e); - } + for (EvidenceMatcher e : hint.getRemoveProduct()) { + removeMatchingEvidences(dependency.getProductEvidence().getEvidence(),e); } - for (Evidence e : hint.getRemoveVersion()) { - if (dependency.getVersionEvidence().getEvidence().contains(e)) { - dependency.getVersionEvidence().getEvidence().remove(e); - } + for (EvidenceMatcher e : hint.getRemoveVersion()) { + removeMatchingEvidences(dependency.getVersionEvidence().getEvidence(),e); } } } @@ -210,6 +206,24 @@ public class HintAnalyzer extends AbstractAnalyzer { } } + private boolean hasMatchingEvidence(Set evidences, EvidenceMatcher criterion) { + for (Evidence evidence : evidences) { + if (criterion.matches(evidence)) { + return true; + } + } + return false; + } + + private void removeMatchingEvidences(Set evidences, EvidenceMatcher e) { + for (Iterator it = evidences.iterator();it.hasNext();) { + Evidence evidence = it.next(); + if (e.matches(evidence)) { + it.remove(); + } + } + } + /** * Loads the hint rules file. * diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/EvidenceMatcher.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/EvidenceMatcher.java new file mode 100644 index 000000000..c7dcafd68 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/EvidenceMatcher.java @@ -0,0 +1,192 @@ +/* + * This file is part of dependency-check-core. + * + * 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) 2017 Hans Aikema. All Rights Reserved. + */ +package org.owasp.dependencycheck.xml.hints; + +import java.util.regex.Pattern; +import org.owasp.dependencycheck.dependency.Confidence; +import org.owasp.dependencycheck.dependency.Evidence; + +/** + * EvidenceMatcher can match one or more {@link Evidence}s. + * By using regular expressions for some fields and allowing omission of + * Evidence fields it can be used to match more than one occurrence of evidence + * to enable hints that work for a range of similar false positives/false negatives. + * + * The EvidenceMatcher is used for processing Evidences of a project's + * dependencies in conjuction with the {@code } and {@code } + * clauses of the hints file. + * + * @author Hans Aikema + */ +public class EvidenceMatcher { + + /** + * Creates a new EvidenceMatcher objects. + * + * @param source the source of the evidence, a source that is {@code null} indicates any source should match. + * @param name the non-{@code null} name of the evidence. + * @param value the non-{@code null} value of the evidence. + * @param regex whether value is a regex. + * @param confidence the confidence of the evidence, a confidence that is {@code null} indicates any confidence should match. + */ + public EvidenceMatcher(String source, String name, String value, boolean regex, Confidence confidence) { + this.source = source; + this.name = name; + this.value = value; + this.confidence = confidence; + this.regex = regex; + } + + /** + * The name that the {@link Evidence} should have for a match. + */ + private String name; + + /** + * The source that the {@link Evidence} should have for a match. + * A {@code null}-value is allowed and functions as a wildcard. + */ + private String source; + + /** + * The value that the {@link Evidence} should have for a match. + */ + private String value; + + /** + * Whether the {@link EvidenceMatcher#value} should be interpreted as a + * regular expression. + */ + private boolean regex=false; + + + /** + * The confidence that the {@link Evidence} should have for a match. + * A {@code null}-value is allowed and functions as a wildcard. + */ + private Confidence confidence; + + /** + * Tests whether the given Evidence matches this EvidenceMatcher. + * @param evidence + * @return whehter the evidence matches this matcher. + */ + public boolean matches(Evidence evidence) { + return sourceMatches(evidence) + && confidenceMatches(evidence) + && name.equalsIgnoreCase(evidence.getName()) + && valueMatches(evidence); + } + + /** + * Standard toString() implementation. + * + * @return the string representation of the object + */ + @Override + public String toString() { + return "HintEvidenceMatcher{" + "name=" + name + ", source=" + source + ", value=" + value + ", confidence=" + confidence + ", regex=" + regex +'}'; + } + + /** + * package-private getter to allow testability of the parser without mocking + * @return The name property + */ + String getName() { + return name; + } + + /** + * package-private getter to allow testability of the parser without mocking + * @return The source property + */ + String getSource() { + return source; + } + + /** + * package-private getter to allow testability of the parser without mocking + * @return The value property + */ + String getValue() { + return value; + } + + /** + * package-private getter to allow testability of the parser without mocking + * @return The regex property + */ + boolean isRegex() { + return regex; + } + + /** + * package-private getter to allow testability of the parser without mocking + * @return The confidence property + */ + Confidence getConfidence() { + return confidence; + } + + /** + * Checks whether the value of the evidence matches this matcher. + * When {@link #isRegEx()} is {@code true} value is used as a + * {@link java.util.regex.Pattern} that it should match. Otherwise the + * value must be case-insensitive equal to the evidence's value. + * + * Uses {@link Evidence#getValue(java.lang.Boolean) to avoid setting + * evidences to used while just checking for a match. + */ + private boolean valueMatches(Evidence evidence) { + boolean result; + if (regex) { + result = Pattern.matches(value, evidence.getValue(Boolean.FALSE)); + } else { + result = value.equalsIgnoreCase(evidence.getValue(Boolean.FALSE)); + } + return result; + } + + /** + * Checks whether the source of the evidence matches this matcher. + * If our source is {@code null} any source in the evidence matches. + * Otherwise the source in the evidence must be case-insensitive equal to + * our source. + * + * @param evidence The evidence to inspect + * @return {@code true} is the source of the evidence matches, false otherwise. + */ + private boolean sourceMatches(Evidence evidence) { + return this.source == null || source.equalsIgnoreCase(evidence.getSource()); + } + + /** + * Checks whether the confidence of the evidence matches this matcher. + * If our confidence is {@code null} any confidence in the evidence matches. + * Otherwise the confidence in the evidence must be exactly equal to our + * confidence. + * + * @param evidence The evidence to inspect + * @return {@code true} is the confidence of the evidence matches, false otherwise. + */ + private boolean confidenceMatches(Evidence evidence) { + return this.confidence == null || confidence.equals(evidence.getConfidence()); + } + + +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintHandler.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintHandler.java index 676067c5e..d9e3be69f 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintHandler.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintHandler.java @@ -20,6 +20,7 @@ package org.owasp.dependencycheck.xml.hints; import java.util.ArrayList; import java.util.List; import org.owasp.dependencycheck.dependency.Confidence; +import org.owasp.dependencycheck.utils.XmlUtils; import org.owasp.dependencycheck.xml.suppression.PropertyType; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -199,7 +200,20 @@ public class HintHandler extends DefaultHandler { final String source = attr.getValue(SOURCE); final String name = attr.getValue(NAME); final String value = attr.getValue(VALUE); - final Confidence confidence = Confidence.valueOf(attr.getValue(CONFIDENCE)); + final Confidence confidence; + final String confidenceAttribute = attr.getValue(CONFIDENCE); + if (confidenceAttribute == null) { + confidence = null; + } else { + confidence = Confidence.valueOf(confidenceAttribute); + } + final boolean regex; + final String regexAttribute = attr.getValue(REGEX); + if (regexAttribute == null) { + regex = false; + } else { + regex = XmlUtils.parseBoolean(regexAttribute); + } switch (hintType) { case VENDOR: switch (nodeType) { @@ -207,10 +221,10 @@ public class HintHandler extends DefaultHandler { rule.addAddVendor(source, name, value, confidence); break; case REMOVE: - rule.addRemoveVendor(source, name, value, confidence); + rule.addRemoveVendor(source, name, value, regex, confidence); break; case GIVEN: - rule.addGivenVendor(source, name, value, confidence); + rule.addGivenVendor(source, name, value, regex, confidence); break; default: break; @@ -222,10 +236,10 @@ public class HintHandler extends DefaultHandler { rule.addAddProduct(source, name, value, confidence); break; case REMOVE: - rule.addRemoveProduct(source, name, value, confidence); + rule.addRemoveProduct(source, name, value, regex, confidence); break; case GIVEN: - rule.addGivenProduct(source, name, value, confidence); + rule.addGivenProduct(source, name, value, regex, confidence); break; default: break; @@ -237,10 +251,10 @@ public class HintHandler extends DefaultHandler { rule.addAddVersion(source, name, value, confidence); break; case REMOVE: - rule.addRemoveVersion(source, name, value, confidence); + rule.addRemoveVersion(source, name, value, regex, confidence); break; case GIVEN: - rule.addGivenVersion(source, name, value, confidence); + rule.addGivenVersion(source, name, value, regex, confidence); break; default: break; diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintParser.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintParser.java index 7f440049a..66a12ee09 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintParser.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintParser.java @@ -66,12 +66,17 @@ public class HintParser { /** * The schema for the hint XML files. */ - private static final String HINT_SCHEMA = "schema/dependency-hint.1.2.xsd"; + private static final String HINT_SCHEMA_1_2 = "schema/dependency-hint.1.2.xsd"; /** * The schema for the hint XML files. */ - private static final String HINT_SCHEMA_OLD = "schema/dependency-hint.1.1.xsd"; + private static final String HINT_SCHEMA_1_1 = "schema/dependency-hint.1.1.xsd"; + + /** + * The schema for the hint XML files. + */ + private static final String HINT_SCHEMA_1_3 = "schema/dependency-hint.1.3.xsd"; /** * Parses the given XML file and returns a list of the hints contained. @@ -82,19 +87,11 @@ public class HintParser { */ public Hints parseHints(File file) throws HintParseException { //TODO there must be a better way to determine which schema to use for validation. - try { - try (FileInputStream fis = new FileInputStream(file)) { + try (FileInputStream fis = new FileInputStream(file)) { return parseHints(fis); - } catch (IOException ex) { - LOGGER.debug("", ex); - throw new HintParseException(ex); - } - } catch (SAXException ex) { - try (FileInputStream fis = new FileInputStream(file)) { - return parseHints(fis, HINT_SCHEMA_OLD); - } catch (SAXException | IOException ex1) { - throw new HintParseException(ex); - } + } catch (SAXException | IOException ex) { + LOGGER.debug("", ex); + throw new HintParseException(ex); } } @@ -108,23 +105,13 @@ public class HintParser { * @throws SAXException thrown if the XML cannot be parsed */ public Hints parseHints(InputStream inputStream) throws HintParseException, SAXException { - return parseHints(inputStream, HINT_SCHEMA); - } - - /** - * Parses the given XML stream and returns a list of the hint rules - * contained. - * - * @param inputStream an InputStream containing hint rules - * @param schema the XSD to use to validate the XML against - * @return a list of hint rules - * @throws HintParseException thrown if the XML cannot be parsed - * @throws SAXException thrown if the XML cannot be parsed - */ - private Hints parseHints(InputStream inputStream, String schema) throws HintParseException, SAXException { - try (InputStream schemaStream = FileUtils.getResourceAsStream(schema)) { + try ( + InputStream schemaStream13 = FileUtils.getResourceAsStream(HINT_SCHEMA_1_3); + InputStream schemaStream12 = FileUtils.getResourceAsStream(HINT_SCHEMA_1_2); + InputStream schemaStream11 = FileUtils.getResourceAsStream(HINT_SCHEMA_1_1); + ) { final HintHandler handler = new HintHandler(); - final SAXParser saxParser = XmlUtils.buildSecureSaxParser(schemaStream); + final SAXParser saxParser = XmlUtils.buildSecureSaxParser(schemaStream13, schemaStream12, schemaStream11); final XMLReader xmlReader = saxParser.getXMLReader(); xmlReader.setErrorHandler(new HintErrorHandler()); xmlReader.setContentHandler(handler); diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintRule.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintRule.java index e92cbdda9..b56712678 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintRule.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/xml/hints/HintRule.java @@ -39,15 +39,15 @@ public class HintRule { /** * The list of vendor evidence that is being matched. */ - private final List givenVendor = new ArrayList<>(); + private final List givenVendor = new ArrayList<>(); /** * The list of product evidence that is being matched. */ - private final List givenProduct = new ArrayList<>(); + private final List givenProduct = new ArrayList<>(); /** * The list of product evidence that is being matched. */ - private final List givenVersion = new ArrayList<>(); + private final List givenVersion = new ArrayList<>(); /** * The list of vendor hints to add. */ @@ -62,17 +62,17 @@ public class HintRule { private final List addVersion = new ArrayList<>(); /** - * The list of vendor hints to add. + * The list of vendor hints to remove. */ - private final List removeVendor = new ArrayList<>(); + private final List removeVendor = new ArrayList<>(); /** - * The list of product evidence to add. + * The list of product evidence to remove. */ - private final List removeProduct = new ArrayList<>(); + private final List removeProduct = new ArrayList<>(); /** - * The list of version evidence to add. + * The list of version evidence to remove. */ - private final List removeVersion = new ArrayList<>(); + private final List removeVersion = new ArrayList<>(); /** * Adds the filename evidence to the collection. @@ -98,10 +98,11 @@ public class HintRule { * @param source the source of the evidence * @param name the name of the evidence * @param value the value of the evidence + * @param regex whether value is a regex * @param confidence the confidence of the evidence */ - public void addGivenProduct(String source, String name, String value, Confidence confidence) { - givenProduct.add(new Evidence(source, name, value, confidence)); + public void addGivenProduct(String source, String name, String value, boolean regex, Confidence confidence) { + givenProduct.add(new EvidenceMatcher(source, name, value, regex, confidence)); } /** @@ -109,7 +110,7 @@ public class HintRule { * * @return the value of givenProduct */ - public List getGivenProduct() { + public List getGivenProduct() { return givenProduct; } @@ -119,10 +120,11 @@ public class HintRule { * @param source the source of the evidence * @param name the name of the evidence * @param value the value of the evidence + * @param regex whether value is a regex * @param confidence the confidence of the evidence */ - public void addGivenVendor(String source, String name, String value, Confidence confidence) { - givenVendor.add(new Evidence(source, name, value, confidence)); + public void addGivenVendor(String source, String name, String value, boolean regex, Confidence confidence) { + givenVendor.add(new EvidenceMatcher(source, name, value, regex, confidence)); } /** @@ -130,7 +132,7 @@ public class HintRule { * * @return the value of givenVendor */ - public List getGivenVendor() { + public List getGivenVendor() { return givenVendor; } @@ -203,17 +205,18 @@ public class HintRule { * @param source the source of the evidence * @param name the name of the evidence * @param value the value of the evidence + * @param regex whether value is a regex * @param confidence the confidence of the evidence */ - public void addRemoveVendor(String source, String name, String value, Confidence confidence) { - removeVendor.add(new Evidence(source, name, value, confidence)); + public void addRemoveVendor(String source, String name, String value, boolean regex, Confidence confidence) { + removeVendor.add(new EvidenceMatcher(source, name, value, regex, confidence)); } /** * Get the value of removeVendor. * * @return the value of removeVendor */ - public List getRemoveVendor() { + public List getRemoveVendor() { return removeVendor; } /** @@ -222,17 +225,18 @@ public class HintRule { * @param source the source of the evidence * @param name the name of the evidence * @param value the value of the evidence + * @param regex whether value is a regex * @param confidence the confidence of the evidence */ - public void addRemoveProduct(String source, String name, String value, Confidence confidence) { - removeProduct.add(new Evidence(source, name, value, confidence)); + public void addRemoveProduct(String source, String name, String value, boolean regex, Confidence confidence) { + removeProduct.add(new EvidenceMatcher(source, name, value, regex, confidence)); } /** * Get the value of removeProduct. * * @return the value of removeProduct */ - public List getRemoveProduct() { + public List getRemoveProduct() { return removeProduct; } /** @@ -241,17 +245,18 @@ public class HintRule { * @param source the source of the evidence * @param name the name of the evidence * @param value the value of the evidence + * @param regex whether value is a regex * @param confidence the confidence of the evidence */ - public void addRemoveVersion(String source, String name, String value, Confidence confidence) { - removeVersion.add(new Evidence(source, name, value, confidence)); + public void addRemoveVersion(String source, String name, String value, boolean regex, Confidence confidence) { + removeVersion.add(new EvidenceMatcher(source, name, value, regex, confidence)); } /** * Get the value of removeVersion. * * @return the value of removeVersion */ - public List getRemoveVersion() { + public List getRemoveVersion() { return removeVersion; } /** @@ -260,17 +265,18 @@ public class HintRule { * @param source the source of the evidence * @param name the name of the evidence * @param value the value of the evidence + * @param regex whether value is a regex * @param confidence the confidence of the evidence */ - public void addGivenVersion(String source, String name, String value, Confidence confidence) { - givenVersion.add(new Evidence(source, name, value, confidence)); + public void addGivenVersion(String source, String name, String value, boolean regex, Confidence confidence) { + givenVersion.add(new EvidenceMatcher(source, name, value, regex, confidence)); } /** * Get the value of givenVersion. * * @return the value of givenVersion */ - public List getGivenVersion() { + public List getGivenVersion() { return givenVersion; } } diff --git a/dependency-check-core/src/main/resources/schema/dependency-hint.1.3.xsd b/dependency-check-core/src/main/resources/schema/dependency-hint.1.3.xsd new file mode 100644 index 000000000..c84cf2980 --- /dev/null +++ b/dependency-check-core/src/main/resources/schema/dependency-hint.1.3.xsd @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/EvidenceMatcherTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/EvidenceMatcherTest.java new file mode 100644 index 000000000..f45f7337e --- /dev/null +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/EvidenceMatcherTest.java @@ -0,0 +1,117 @@ +/* + * Copyright 2017 OWASP. + * + * 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. + */ +package org.owasp.dependencycheck.xml.hints; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import org.junit.Test; +import org.owasp.dependencycheck.dependency.Confidence; +import org.owasp.dependencycheck.dependency.Evidence; + +/** + * Unit tests for {@link EvidenceMatcher}. + * + * @author Hans Aikema + */ +public class EvidenceMatcherTest { + + private static final Evidence EVIDENCE_HIGHEST = new Evidence("source", "name", "value", Confidence.HIGHEST); + private static final Evidence EVIDENCE_HIGH = new Evidence("source", "name", "value", Confidence.HIGH); + private static final Evidence EVIDENCE_MEDIUM = new Evidence("source", "name", "value", Confidence.MEDIUM); + private static final Evidence EVIDENCE_MEDIUM_SECOND_SOURCE = new Evidence("source 2", "name", "value", Confidence.MEDIUM); + private static final Evidence EVIDENCE_LOW = new Evidence("source", "name", "value", Confidence.LOW); + + @Test + public void testExactMatching() throws Exception { + final EvidenceMatcher exactMatcherHighest = new EvidenceMatcher("source", "name", "value", false, Confidence.HIGHEST); + assertTrue("exact matcher should match EVIDENCE_HIGHEST", exactMatcherHighest.matches(EVIDENCE_HIGHEST)); + assertFalse("exact matcher should not match EVIDENCE_HIGH", exactMatcherHighest.matches(EVIDENCE_HIGH)); + assertFalse("exact matcher should not match EVIDENCE_MEDIUM", exactMatcherHighest.matches(EVIDENCE_MEDIUM)); + assertFalse("exact matcher should not match EVIDENCE_MEDIUM_SECOND_SOURCE", exactMatcherHighest.matches(EVIDENCE_MEDIUM_SECOND_SOURCE)); + assertFalse("exact matcher should not match EVIDENCE_LOW", exactMatcherHighest.matches(EVIDENCE_LOW)); + } + + @Test + public void testWildcardConfidenceMatching() throws Exception { + final EvidenceMatcher wildcardCofidenceMatcher = new EvidenceMatcher("source", "name", "value", false, null); + assertTrue("wildcard confidence matcher should match EVIDENCE_HIGHEST", wildcardCofidenceMatcher.matches(EVIDENCE_HIGHEST)); + assertTrue("wildcard confidence matcher should match EVIDENCE_HIGH", wildcardCofidenceMatcher.matches(EVIDENCE_HIGH)); + assertTrue("wildcard confidence matcher should match EVIDENCE_MEDIUM", wildcardCofidenceMatcher.matches(EVIDENCE_MEDIUM)); + assertFalse("wildcard confidence matcher should not match EVIDENCE_MEDIUM_SECOND_SOURCE", wildcardCofidenceMatcher.matches(EVIDENCE_MEDIUM_SECOND_SOURCE)); + assertTrue("wildcard confidence matcher should match EVIDENCE_LOW", wildcardCofidenceMatcher.matches(EVIDENCE_LOW)); + } + + @Test + public void testWildcardSourceMatching() throws Exception { + final EvidenceMatcher wildcardSourceMatcher = new EvidenceMatcher(null, "name", "value", false, Confidence.MEDIUM); + assertFalse("wildcard source matcher should not match EVIDENCE_HIGHEST", wildcardSourceMatcher.matches(EVIDENCE_HIGHEST)); + assertFalse("wildcard source matcher should not match EVIDENCE_HIGH", wildcardSourceMatcher.matches(EVIDENCE_HIGH)); + assertTrue("wildcard source matcher should match EVIDENCE_MEDIUM", wildcardSourceMatcher.matches(EVIDENCE_MEDIUM)); + assertTrue("wildcard source matcher should match EVIDENCE_MEDIUM_SECOND_SOURCE", wildcardSourceMatcher.matches(EVIDENCE_MEDIUM_SECOND_SOURCE)); + assertFalse("wildcard source matcher should not match EVIDENCE_LOW", wildcardSourceMatcher.matches(EVIDENCE_LOW)); + } + + private static final Evidence REGEX_EVIDENCE_HIGHEST = new Evidence("source", "name", "value 1", Confidence.HIGHEST); + private static final Evidence REGEX_EVIDENCE_HIGH = new Evidence("source", "name", "value 2", Confidence.HIGH); + private static final Evidence REGEX_EVIDENCE_MEDIUM = new Evidence("source", "name", "Value will not match because of case", Confidence.MEDIUM); + private static final Evidence REGEX_EVIDENCE_MEDIUM_SECOND_SOURCE = new Evidence("source 2", "name", "yet another value that will match", Confidence.MEDIUM); + private static final Evidence REGEX_EVIDENCE_MEDIUM_THIRD_SOURCE = new Evidence("source 3", "name", "and even more values to match", Confidence.MEDIUM); + private static final Evidence REGEX_EVIDENCE_LOW = new Evidence("source", "name", "val that should not match", Confidence.LOW); + + @Test + public void testRegExMatching() throws Exception { + final EvidenceMatcher regexMediumMatcher = new EvidenceMatcher("source 2", "name", ".*value.*", true, Confidence.MEDIUM); + assertFalse("regex medium matcher should not match REGEX_EVIDENCE_HIGHEST", regexMediumMatcher.matches(REGEX_EVIDENCE_HIGHEST)); + assertFalse("regex medium matcher should not match REGEX_EVIDENCE_HIGH", regexMediumMatcher.matches(REGEX_EVIDENCE_HIGH)); + assertFalse("regex medium matcher should not match REGEX_EVIDENCE_MEDIUM", regexMediumMatcher.matches(REGEX_EVIDENCE_MEDIUM)); + assertTrue("regex medium matcher should match REGEX_EVIDENCE_MEDIUM_SECOND_SOURCE", regexMediumMatcher.matches(REGEX_EVIDENCE_MEDIUM_SECOND_SOURCE)); + assertFalse("regex medium matcher should not match REGEX_EVIDENCE_MEDIUM_THIRD_SOURCE", regexMediumMatcher.matches(REGEX_EVIDENCE_MEDIUM_THIRD_SOURCE)); + assertFalse("regex medium matcher should not match REGEX_EVIDENCE_LOW", regexMediumMatcher.matches(REGEX_EVIDENCE_LOW)); + } + + @Test + public void testRegExWildcardSourceMatching() throws Exception { + final EvidenceMatcher regexMediumWildcardSourceMatcher = new EvidenceMatcher(null, "name", "^.*v[al]{2,2}ue[a-z ]+$", true, Confidence.MEDIUM); + assertFalse("regex medium wildcard source matcher should not match REGEX_EVIDENCE_HIGHEST", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_HIGHEST)); + assertFalse("regex medium wildcard source matcher should not match REGEX_EVIDENCE_HIGH", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_HIGH)); + assertFalse("regex medium wildcard source matcher should not match REGEX_EVIDENCE_MEDIUM", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_MEDIUM)); + assertTrue("regex medium wildcard source matcher should match REGEX_EVIDENCE_MEDIUM_SECOND_SOURCE", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_MEDIUM_SECOND_SOURCE)); + assertTrue("regex medium wildcard source matcher should match REGEX_EVIDENCE_MEDIUM_THIRD_SOURCE", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_MEDIUM_THIRD_SOURCE)); + assertFalse("regex medium wildcard source matcher should not match REGEX_EVIDENCE_LOW", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_LOW)); + } + + @Test + public void testRegExWildcardSourceWildcardConfidenceMatching() throws Exception { + final EvidenceMatcher regexMediumWildcardSourceMatcher = new EvidenceMatcher(null, "name", ".*value.*", true, null); + assertTrue("regex wildcard source wildcard confidence matcher should match REGEX_EVIDENCE_HIGHEST", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_HIGHEST)); + assertTrue("regex wildcard source wildcard confidence matcher should match REGEX_EVIDENCE_HIGH", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_HIGH)); + assertFalse("regex wildcard source wildcard confidence matcher should not match REGEX_EVIDENCE_MEDIUM", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_MEDIUM)); + assertTrue("regex wildcard source wildcard confidence matcher should match REGEX_EVIDENCE_MEDIUM_SECOND_SOURCE", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_MEDIUM_SECOND_SOURCE)); + assertTrue("regex wildcard source wildcard confidence matcher should match REGEX_EVIDENCE_MEDIUM_THIRD_SOURCE", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_MEDIUM_THIRD_SOURCE)); + assertFalse("regex wildcard source wildcard confidence matcher should match REGEX_EVIDENCE_LOW", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_LOW)); + } + + @Test + public void testRegExWildcardSourceWildcardConfidenceFourMatching() throws Exception { + final EvidenceMatcher regexMediumWildcardSourceMatcher = new EvidenceMatcher(null, "name", "^.*[Vv][al]{2,2}[a-z ]+$", true, null); + assertFalse("regex wildcard source wildcard confidence matcher should not match REGEX_EVIDENCE_HIGHEST", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_HIGHEST)); + assertFalse("regex wildcard source wildcard confidence matcher should not match REGEX_EVIDENCE_HIGH", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_HIGH)); + assertTrue("regex wildcard source wildcard confidence matcher should match REGEX_EVIDENCE_MEDIUM", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_MEDIUM)); + assertTrue("regex wildcard source wildcard confidence matcher should match REGEX_EVIDENCE_MEDIUM_SECOND_SOURCE", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_MEDIUM_SECOND_SOURCE)); + assertTrue("regex wildcard source wildcard confidence matcher should match REGEX_EVIDENCE_MEDIUM_THIRD_SOURCE", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_MEDIUM_THIRD_SOURCE)); + assertTrue("regex wildcard source wildcard confidence matcher should match REGEX_EVIDENCE_LOW", regexMediumWildcardSourceMatcher.matches(REGEX_EVIDENCE_LOW)); + } +} diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/HintParserTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/HintParserTest.java index 7c6432cc4..c37646f4c 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/HintParserTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/xml/hints/HintParserTest.java @@ -19,8 +19,11 @@ package org.owasp.dependencycheck.xml.hints; import java.io.File; import java.io.InputStream; +import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.assertEquals; +import org.junit.Rule; +import org.junit.rules.ExpectedException; import org.owasp.dependencycheck.BaseTest; /** @@ -29,6 +32,9 @@ import org.owasp.dependencycheck.BaseTest; */ public class HintParserTest extends BaseTest { + @Rule + public ExpectedException thrown= ExpectedException.none(); + /** * Test of parseHints method, of class HintParser. */ @@ -59,6 +65,27 @@ public class HintParserTest extends BaseTest { assertEquals("sun duplicates vendor oracle", "oracle", results.getVendorDuplicatingHintRules().get(0).getDuplicate()); } + /** + * Test the application of the correct XSD by the parser by using a + * hints-file with namespace + * {@code https://jeremylong.github.io/DependencyCheck/dependency-hint.1.1.xsd} + * that is using the version evidence for {@code} that was introduced + * with namespace + * {@code https://jeremylong.github.io/DependencyCheck/dependency-hint.1.2.xsd}. + * This should yield a specific SAXParseException that gets wrapped into a + * HintParseException. We check for the correct error by searching for the + * error-message of the SAXParser in the exception's message. + */ + @Test + public void testParseHintsXSDSelection() throws Exception { + thrown.expect(org.owasp.dependencycheck.xml.hints.HintParseException.class); + thrown.expectMessage("Line=7, Column=133: cvc-enumeration-valid: Value 'version' is not facet-valid with respect to enumeration '[vendor, product]'. It must be a value from the enumeration."); + File file = BaseTest.getResourceAsFile(this, "hints_invalid.xml"); + HintParser instance = new HintParser(); + instance.parseHints(file); + Assert.fail("A parser exception for an XML-schema violation should have been thrown"); + } + /** * Test of parseHints method, of class HintParser. */ @@ -107,4 +134,60 @@ public class HintParserTest extends BaseTest { assertEquals("add version name not found in hint 1", "remove version name", results.getHintRules().get(1).getRemoveVersion().get(0).getName()); } + + /** + * Test of parseHints method, of class HintParser. + */ + @Test + public void testParseHintsWithRegex() throws Exception { + InputStream ins = BaseTest.getResourceAsStream(this, "hints_13.xml"); + HintParser instance = new HintParser(); + Hints results = instance.parseHints(ins); + assertEquals("Zero duplicating hints should have been read", 0, results.getVendorDuplicatingHintRules().size()); + assertEquals("Two hint rules should have been read", 2, results.getHintRules().size()); + + assertEquals("One given product should have been read in hint 0", 1, results.getHintRules().get(0).getGivenProduct().size()); + assertEquals("One given vendor should have been read in hint 0", 1, results.getHintRules().get(0).getGivenVendor().size()); + assertEquals("One given version should have been read in hint 0", 1, results.getHintRules().get(0).getGivenVersion().size()); + + assertEquals("One add product should have been read in hint 0", 1, results.getHintRules().get(0).getAddProduct().size()); + assertEquals("One add vendor should have been read in hint 0", 1, results.getHintRules().get(0).getAddVendor().size()); + assertEquals("One add version should have been read in hint 0", 1, results.getHintRules().get(0).getAddVersion().size()); + assertEquals("Zero remove product should have been read in hint 0", 0, results.getHintRules().get(0).getRemoveProduct().size()); + assertEquals("Zero remove vendor should have been read in hint 0", 0, results.getHintRules().get(0).getRemoveVendor().size()); + assertEquals("Zero remove version should have been read in hint 0", 0, results.getHintRules().get(0).getRemoveVersion().size()); + + assertEquals("Zero given product should have been read in hint 1", 0, results.getHintRules().get(1).getGivenProduct().size()); + assertEquals("Zero given vendor should have been read in hint 1", 0, results.getHintRules().get(1).getGivenVendor().size()); + assertEquals("One given version should have been read in hint 1", 1, results.getHintRules().get(1).getGivenVersion().size()); + + assertEquals("One remove product should have been read in hint 1", 1, results.getHintRules().get(1).getRemoveProduct().size()); + assertEquals("One remove vendor should have been read in hint 1", 1, results.getHintRules().get(1).getRemoveVendor().size()); + assertEquals("One remove version should have been read in hint 1", 1, results.getHintRules().get(1).getRemoveVersion().size()); + assertEquals("Zero add product should have been read in hint 1", 0, results.getHintRules().get(1).getAddProduct().size()); + assertEquals("Zero add vendor should have been read in hint 1", 0, results.getHintRules().get(1).getAddVendor().size()); + assertEquals("Zero add version should have been read in hint 1", 0, results.getHintRules().get(1).getAddVersion().size()); + + assertEquals("add product name not found in hint 0", "add product name", results.getHintRules().get(0).getAddProduct().get(0).getName()); + assertEquals("add vendor name not found in hint 0", "add vendor name", results.getHintRules().get(0).getAddVendor().get(0).getName()); + assertEquals("add version name not found in hint 0", "add version name", results.getHintRules().get(0).getAddVersion().get(0).getName()); + + assertEquals("given product name not found in hint 0", "given product name", results.getHintRules().get(0).getGivenProduct().get(0).getName()); + assertEquals("value not registered to be a regex for given product in hint 0", true, results.getHintRules().get(0).getGivenProduct().get(0).isRegex()); + assertEquals("given vendor name not found in hint 0", "given vendor name", results.getHintRules().get(0).getGivenVendor().get(0).getName()); + assertEquals("value not registered to be a regex for given vendor in hint 0", true, results.getHintRules().get(0).getGivenVendor().get(0).isRegex()); + assertEquals("given version name not found in hint 0", "given version name", results.getHintRules().get(0).getGivenVersion().get(0).getName()); + assertEquals("value not registered to not be a regex for given version in hint 0", false, results.getHintRules().get(0).getGivenVersion().get(0).isRegex()); + + assertEquals("given version name not found in hint 1", "given version name", results.getHintRules().get(1).getGivenVersion().get(0).getName()); + assertEquals("value not registered to not be a regex by default for given version in hint 1", false, results.getHintRules().get(1).getRemoveProduct().get(0).isRegex()); + + assertEquals("remove product name not found in hint 1", "remove product name", results.getHintRules().get(1).getRemoveProduct().get(0).getName()); + assertEquals("value not registered to not be a regex for product removal in hint 1", false, results.getHintRules().get(1).getRemoveProduct().get(0).isRegex()); + assertEquals("remove vendor name not found in hint 1", "remove vendor name", results.getHintRules().get(1).getRemoveVendor().get(0).getName()); + assertEquals("value not registered to not be a regex for vendor removal in hint 1", false, results.getHintRules().get(1).getRemoveVendor().get(0).isRegex()); + assertEquals("remove version name not found in hint 1", "remove version name", results.getHintRules().get(1).getRemoveVersion().get(0).getName()); + assertEquals("value not defaulted to not be a regex for vendor removal in hint 1", false, results.getHintRules().get(1).getRemoveVersion().get(0).isRegex()); + + } } diff --git a/dependency-check-core/src/test/resources/hints_13.xml b/dependency-check-core/src/test/resources/hints_13.xml new file mode 100644 index 000000000..42d7dd89d --- /dev/null +++ b/dependency-check-core/src/test/resources/hints_13.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dependency-check-core/src/test/resources/hints_invalid.xml b/dependency-check-core/src/test/resources/hints_invalid.xml new file mode 100644 index 000000000..96e7099a6 --- /dev/null +++ b/dependency-check-core/src/test/resources/hints_invalid.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/XmlUtils.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/XmlUtils.java index 9d81a2045..8c52c8c63 100644 --- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/XmlUtils.java +++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/XmlUtils.java @@ -61,7 +61,7 @@ public final class XmlUtils { /** * Constructs a validating secure SAX Parser. * - * @param schemaStream the schema to validate the XML against + * @param schemaStream One or more inputStreams with the schema(s) that the parser should be able to validate the XML against, one InputStream per schema * @return a SAX Parser * @throws ParserConfigurationException is thrown if there is a parser * configuration exception @@ -71,7 +71,7 @@ public final class XmlUtils { * feature * @throws SAXException is thrown if there is a SAXException */ - public static SAXParser buildSecureSaxParser(InputStream schemaStream) throws ParserConfigurationException, + public static SAXParser buildSecureSaxParser(InputStream... schemaStream) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException, SAXException { final SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); @@ -87,6 +87,32 @@ public final class XmlUtils { return saxParser; } + /** + * Converts an attribute value representing an xsd:boolean value to a boolean using + * the rules as stated in the XML specification + * + * @param lexicalXSDBoolean The string-value of the boolean + * @return the boolean value represented by {@code lexicalXSDBoolean} + * @throws IllegalArgumentException When {@code lexicalXSDBoolean} does + * fit the lexical space of the XSD boolean datatype + */ + public static boolean parseBoolean(String lexicalXSDBoolean) { + final boolean result; + switch (lexicalXSDBoolean) { + case "true": + case "1": + result = true; + break; + case "false": + case "0": + result = false; + break; + default: + throw new IllegalArgumentException("'"+lexicalXSDBoolean+"' is not a valid xs:boolean value"); + } + return result; + } + /** * Constructs a secure SAX Parser. *