1 /*
2  * This file is part of dependency-check-core.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * Copyright (c) 2017 Jeremy Long. All Rights Reserved.
17  */
18 package org.owasp.dependencycheck.analyzer;
19 
20 import java.util.HashSet;
21 import java.util.Objects;
22 import java.util.Set;
23 import javax.annotation.concurrent.ThreadSafe;
24 import org.owasp.dependencycheck.Engine;
25 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
26 import org.owasp.dependencycheck.dependency.Dependency;
27 import org.owasp.dependencycheck.dependency.Evidence;
28 import org.owasp.dependencycheck.dependency.EvidenceType;
29 import org.owasp.dependencycheck.utils.DependencyVersion;
30 import org.owasp.dependencycheck.utils.Settings;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33 
34 /**
35  * This analyzer attempts to filter out erroneous version numbers collected.
36  * Initially, this will focus on JAR files that contain a POM version number
37  * that matches the file name - if identified all other version information will
38  * be removed.
39  *
40  * @author Jeremy Long
41  */
42 @ThreadSafe
43 public class VersionFilterAnalyzer extends AbstractAnalyzer {
44 
45     /**
46      * The Logger for use throughout the class
47      */
48     private static final Logger LOGGER = LoggerFactory.getLogger(VersionFilterAnalyzer.class);
49 
50     //<editor-fold defaultstate="collapsed" desc="Constants">
51     /**
52      * Evidence source.
53      */
54     private static final String FILE = "file";
55     /**
56      * Evidence source.
57      */
58     private static final String POM = "pom";
59     /**
60      * Evidence source.
61      */
62     private static final String NEXUS = "nexus";
63     /**
64      * Evidence source.
65      */
66     private static final String CENTRAL = "central";
67     /**
68      * Evidence source.
69      */
70     private static final String MANIFEST = "Manifest";
71     /**
72      * Evidence name.
73      */
74     private static final String VERSION = "version";
75     /**
76      * Evidence name.
77      */
78     private static final String IMPLEMENTATION_VERSION = "Implementation-Version";
79 
80     /**
81      * The name of the analyzer.
82      */
83     private static final String ANALYZER_NAME = "Version Filter Analyzer";
84     /**
85      * The phase that this analyzer is intended to run in.
86      */
87     private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.POST_INFORMATION_COLLECTION;
88 
89     //</editor-fold>
90     //<editor-fold defaultstate="collapsed" desc="Standard implementation of Analyzer">
91     /**
92      * Returns the name of the analyzer.
93      *
94      * @return the name of the analyzer.
95      */
96     @Override
getName()97     public String getName() {
98         return ANALYZER_NAME;
99     }
100 
101     /**
102      * Returns the phase that the analyzer is intended to run in.
103      *
104      * @return the phase that the analyzer is intended to run in.
105      */
106     @Override
getAnalysisPhase()107     public AnalysisPhase getAnalysisPhase() {
108         return ANALYSIS_PHASE;
109     }
110 
111     /**
112      * Returns the setting key to determine if the analyzer is enabled.
113      *
114      * @return the key for the analyzer's enabled property
115      */
116     @Override
getAnalyzerEnabledSettingKey()117     protected String getAnalyzerEnabledSettingKey() {
118         return Settings.KEYS.ANALYZER_VERSION_FILTER_ENABLED;
119     }
120     //</editor-fold>
121 
122     /**
123      * The HintAnalyzer uses knowledge about a dependency to add additional
124      * information to help in identification of identifiers or vulnerabilities.
125      *
126      * @param dependency The dependency being analyzed
127      * @param engine The scanning engine
128      * @throws AnalysisException is thrown if there is an exception analyzing
129      * the dependency.
130      */
131     @Override
analyzeDependency(Dependency dependency, Engine engine)132     protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
133         String fileVersion = null;
134         String pomVersion = null;
135         String manifestVersion = null;
136         for (Evidence e : dependency.getEvidence(EvidenceType.VERSION)) {
137             if (FILE.equals(e.getSource()) && VERSION.equals(e.getName())) {
138                 fileVersion = e.getValue();
139             } else if ((NEXUS.equals(e.getSource()) || CENTRAL.equals(e.getSource())
140                     || POM.equals(e.getSource())) && VERSION.equals(e.getName())) {
141                 pomVersion = e.getValue();
142             } else if (MANIFEST.equals(e.getSource()) && IMPLEMENTATION_VERSION.equals(e.getName())) {
143                 manifestVersion = e.getValue();
144             }
145         }
146         //ensure we have at least two not null
147         if (((fileVersion == null ? 0 : 1) + (pomVersion == null ? 0 : 1) + (manifestVersion == null ? 0 : 1)) > 1) {
148             final DependencyVersion dvFile = new DependencyVersion(fileVersion);
149             final DependencyVersion dvPom = new DependencyVersion(pomVersion);
150             final DependencyVersion dvManifest = new DependencyVersion(manifestVersion);
151             final boolean fileMatch = Objects.equals(dvFile, dvPom) || Objects.equals(dvFile, dvManifest);
152             final boolean manifestMatch = Objects.equals(dvManifest, dvPom) || Objects.equals(dvManifest, dvFile);
153             final boolean pomMatch = Objects.equals(dvPom, dvFile) || Objects.equals(dvPom, dvManifest);
154             if (fileMatch || manifestMatch || pomMatch) {
155                 LOGGER.debug("filtering evidence from {}", dependency.getFileName());
156                 final Set<Evidence> remove = new HashSet<>();
157                 for (Evidence e : dependency.getEvidence(EvidenceType.VERSION)) {
158                     if (!(pomMatch && VERSION.equals(e.getName())
159                             && (NEXUS.equals(e.getSource()) || CENTRAL.equals(e.getSource()) || POM.equals(e.getSource())))
160                             && !(fileMatch && VERSION.equals(e.getName()) && FILE.equals(e.getSource()))
161                             && !(manifestMatch && MANIFEST.equals(e.getSource()) && IMPLEMENTATION_VERSION.equals(e.getName()))) {
162                         remove.add(e);
163                     }
164                 }
165                 for (Evidence e : remove) {
166                     dependency.removeEvidence(EvidenceType.VERSION, e);
167                 }
168             }
169         }
170     }
171 }
172