Implement issue #704 and enable multi-schema validation

This commit is contained in:
Hans Aikema
2017-09-17 15:30:48 +02:00
parent ae50b01318
commit 67aa59c4b8
11 changed files with 658 additions and 84 deletions

View File

@@ -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<Evidence> evidences, EvidenceMatcher criterion) {
for (Evidence evidence : evidences) {
if (criterion.matches(evidence)) {
return true;
}
}
return false;
}
private void removeMatchingEvidences(Set<Evidence> evidences, EvidenceMatcher e) {
for (Iterator<Evidence> it = evidences.iterator();it.hasNext();) {
Evidence evidence = it.next();
if (e.matches(evidence)) {
it.remove();
}
}
}
/**
* Loads the hint rules file.
*

View File

@@ -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 <given>} and {@code <remove>}
* 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());
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -39,15 +39,15 @@ public class HintRule {
/**
* The list of vendor evidence that is being matched.
*/
private final List<Evidence> givenVendor = new ArrayList<>();
private final List<EvidenceMatcher> givenVendor = new ArrayList<>();
/**
* The list of product evidence that is being matched.
*/
private final List<Evidence> givenProduct = new ArrayList<>();
private final List<EvidenceMatcher> givenProduct = new ArrayList<>();
/**
* The list of product evidence that is being matched.
*/
private final List<Evidence> givenVersion = new ArrayList<>();
private final List<EvidenceMatcher> givenVersion = new ArrayList<>();
/**
* The list of vendor hints to add.
*/
@@ -62,17 +62,17 @@ public class HintRule {
private final List<Evidence> addVersion = new ArrayList<>();
/**
* The list of vendor hints to add.
* The list of vendor hints to remove.
*/
private final List<Evidence> removeVendor = new ArrayList<>();
private final List<EvidenceMatcher> removeVendor = new ArrayList<>();
/**
* The list of product evidence to add.
* The list of product evidence to remove.
*/
private final List<Evidence> removeProduct = new ArrayList<>();
private final List<EvidenceMatcher> removeProduct = new ArrayList<>();
/**
* The list of version evidence to add.
* The list of version evidence to remove.
*/
private final List<Evidence> removeVersion = new ArrayList<>();
private final List<EvidenceMatcher> 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<Evidence> getGivenProduct() {
public List<EvidenceMatcher> 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<Evidence> getGivenVendor() {
public List<EvidenceMatcher> 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<Evidence> getRemoveVendor() {
public List<EvidenceMatcher> 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<Evidence> getRemoveProduct() {
public List<EvidenceMatcher> 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<Evidence> getRemoveVersion() {
public List<EvidenceMatcher> 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<Evidence> getGivenVersion() {
public List<EvidenceMatcher> getGivenVersion() {
return givenVersion;
}
}

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema id="hints"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="https://jeremylong.github.io/DependencyCheck/dependency-hint.1.3.xsd"
xmlns:dc="https://jeremylong.github.io/DependencyCheck/dependency-hint.1.3.xsd">
<xs:simpleType name="type">
<xs:restriction base="xs:string">
<xs:enumeration value="vendor"/>
<xs:enumeration value="product"/>
<xs:enumeration value="version"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="confidence">
<xs:restriction base="xs:string">
<xs:enumeration value="HIGHEST"/>
<xs:enumeration value="HIGH"/>
<xs:enumeration value="MEDIUM"/>
<xs:enumeration value="LOW"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="evidenceMatcher">
<xs:attribute name="type" use="required" type="dc:type"/>
<xs:attribute name="source" use="optional" type="xs:string"/>
<xs:attribute name="name" use="required" type="xs:string"/>
<xs:attribute name="value" use="required" type="xs:string"/>
<xs:attribute name="regex" use="optional" type="xs:boolean" default="false"/>
<xs:attribute name="confidence" use="optional" type="dc:confidence"/>
</xs:complexType>
<xs:complexType name="evidence">
<xs:attribute name="type" use="required" type="dc:type"/>
<xs:attribute name="source" use="required" type="xs:string"/>
<xs:attribute name="name" use="required" type="xs:string"/>
<xs:attribute name="value" use="required" type="xs:string"/>
<xs:attribute name="confidence" use="required" type="dc:confidence"/>
</xs:complexType>
<xs:complexType name="fileName">
<xs:attribute name="contains" use="required" type="xs:string"/>
<xs:attribute name="regex" use="optional" type="xs:boolean" default="false"/>
<xs:attribute name="caseSensitive" use="optional" type="xs:boolean" default="false"/>
</xs:complexType>
<xs:complexType name="given">
<xs:choice minOccurs="1" maxOccurs="unbounded">
<xs:element name="evidence" type="dc:evidenceMatcher"/>
<xs:element name="fileName" type="dc:fileName"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="add">
<xs:sequence minOccurs="1" maxOccurs="unbounded">
<xs:element name="evidence" type="dc:evidence"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="remove">
<xs:sequence minOccurs="1" maxOccurs="unbounded">
<xs:element name="evidence" type="dc:evidenceMatcher"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="hint">
<xs:sequence minOccurs="1" maxOccurs="1">
<xs:element name="given" type="dc:given"/>
<xs:choice minOccurs="1" maxOccurs="1">
<xs:element name="add" type="dc:add"/>
<xs:element name="remove" type="dc:remove"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<xs:complexType name="duplicatingHint">
<xs:attribute name="value" use="required" type="xs:string"/>
<xs:attribute name="duplicate" use="required" type="xs:string"/>
</xs:complexType>
<xs:element name="hints">
<xs:complexType>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="hint" type="dc:hint"/>
</xs:sequence>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="vendorDuplicatingHint" type="dc:duplicatingHint"/>
</xs:sequence>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -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));
}
}

View File

@@ -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<given>} 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());
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<hints xmlns="https://jeremylong.github.io/DependencyCheck/dependency-hint.1.3.xsd">
<hint>
<given><!-- NOTE: These are OR conditions -->
<evidence type="product" source="product source" name="given product name" value="value" regex="true" confidence="HIGH"/>
<evidence type="vendor" source="vendor source" name="given vendor name" value="value" regex="1" confidence="HIGH"/>
<evidence type="version" source="version source" name="given version name" value="value" regex="false" confidence="HIGH"/>
</given>
<add>
<evidence type="product" source="hint analyzer" name="add product name" value="product" confidence="HIGH"/>
<evidence type="vendor" source="hint analyzer" name="add vendor name" value="vendor" confidence="HIGH"/>
<evidence type="version" source="hint analyzer" name="add version name" value="value" confidence="HIGH"/>
</add>
</hint>
<hint>
<given>
<evidence type="version" source="version source" name="given version name" value="1.2.3" confidence="HIGH"/>
</given>
<remove>
<evidence type="product" source="hint analyzer" name="remove product name" value="product" regex="false" confidence="HIGH"/>
<evidence type="vendor" source="hint analyzer" name="remove vendor name" value="vendor" regex="0" confidence="HIGH"/>
<evidence type="version" source="hint analyzer" name="remove version name" value="value" confidence="HIGH"/>
</remove>
</hint>
</hints>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<hints xmlns="https://jeremylong.github.io/DependencyCheck/dependency-hint.1.1.xsd">
<hint>
<given>
<evidence type="product" source="product source" name="given product name" value="value" confidence="HIGH"/>
<evidence type="vendor" source="vendor source" name="given vendor name" value="value" confidence="HIGH"/>
<evidence type="version" source="version source" name="given version name" value="value" confidence="HIGH"/>
</given>
<add>
<evidence type="product" source="hint analyzer" name="add product name" value="product" confidence="HIGH"/>
<evidence type="vendor" source="hint analyzer" name="add vendor name" value="vendor" confidence="HIGH"/>
</add>
</hint>
<hint>
<given>
<fileName contains="spring"/>
<fileName contains="struts" regex="true" caseSensitive="true"/>
</given>
<add>
<evidence type="product" source="hint analyzer" name="product" value="product" confidence="HIGH"/>
<evidence type="vendor" source="hint analyzer" name="vendor" value="vendor" confidence="HIGH"/>
</add>
</hint>
<vendorDuplicatingHint value="sun" duplicate="oracle"/>
<vendorDuplicatingHint value="oracle" duplicate="sun"/>
</hints>

View File

@@ -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.
*