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) 2014 Jeremy Long. All Rights Reserved.
17  */
18 package org.owasp.dependencycheck.analyzer;
19 
20 import org.apache.commons.io.FileUtils;
21 import org.owasp.dependencycheck.Engine;
22 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
23 import org.owasp.dependencycheck.data.central.CentralSearch;
24 import org.owasp.dependencycheck.data.nexus.MavenArtifact;
25 import org.owasp.dependencycheck.dependency.Confidence;
26 import org.owasp.dependencycheck.dependency.Dependency;
27 import org.owasp.dependencycheck.dependency.Evidence;
28 import org.owasp.dependencycheck.xml.pom.PomUtils;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 
32 import java.io.File;
33 import java.io.FileFilter;
34 import java.io.FileNotFoundException;
35 import java.io.IOException;
36 import java.net.MalformedURLException;
37 import java.net.URL;
38 import java.util.List;
39 import javax.annotation.concurrent.ThreadSafe;
40 import org.owasp.dependencycheck.dependency.EvidenceType;
41 import org.owasp.dependencycheck.exception.InitializationException;
42 import org.owasp.dependencycheck.utils.DownloadFailedException;
43 import org.owasp.dependencycheck.utils.Downloader;
44 import org.owasp.dependencycheck.utils.FileFilterBuilder;
45 import org.owasp.dependencycheck.utils.InvalidSettingException;
46 import org.owasp.dependencycheck.utils.Settings;
47 
48 /**
49  * Analyzer which will attempt to locate a dependency, and the GAV information,
50  * by querying Central for the dependency's SHA-1 digest.
51  *
52  * @author colezlaw
53  */
54 @ThreadSafe
55 public class CentralAnalyzer extends AbstractFileTypeAnalyzer {
56 
57     /**
58      * The logger.
59      */
60     private static final Logger LOGGER = LoggerFactory.getLogger(CentralAnalyzer.class);
61 
62     /**
63      * The name of the analyzer.
64      */
65     private static final String ANALYZER_NAME = "Central Analyzer";
66 
67     /**
68      * The phase in which this analyzer runs.
69      */
70     private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
71 
72     /**
73      * The types of files on which this will work.
74      */
75     private static final String SUPPORTED_EXTENSIONS = "jar";
76 
77     /**
78      * There may be temporary issues when connecting to MavenCentral. In order
79      * to compensate for 99% of the issues, we perform a retry before finally
80      * failing the analysis.
81      */
82     private static final int NUMBER_OF_TRIES = 5;
83 
84     /**
85      * The searcher itself.
86      */
87     private CentralSearch searcher;
88 
89     /**
90      * Initializes the analyzer with the configured settings.
91      *
92      * @param settings the configured settings to use
93      */
94     @Override
initialize(Settings settings)95     public void initialize(Settings settings) {
96         super.initialize(settings);
97         setEnabled(checkEnabled());
98     }
99 
100     /**
101      * Determines if this analyzer is enabled.
102      *
103      * @return <code>true</code> if the analyzer is enabled; otherwise
104      * <code>false</code>
105      */
checkEnabled()106     private boolean checkEnabled() {
107         boolean retVal = false;
108 
109         try {
110             if (getSettings().getBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED)) {
111                 if (!getSettings().getBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED)
112                         || NexusAnalyzer.DEFAULT_URL.equals(getSettings().getString(Settings.KEYS.ANALYZER_NEXUS_URL))) {
113                     LOGGER.debug("Enabling the Central analyzer");
114                     retVal = true;
115                 } else {
116                     LOGGER.info("Nexus analyzer is enabled, disabling the Central Analyzer");
117                 }
118             } else {
119                 LOGGER.info("Central analyzer disabled");
120             }
121         } catch (InvalidSettingException ise) {
122             LOGGER.warn("Invalid setting. Disabling the Central analyzer");
123         }
124         return retVal;
125     }
126 
127     /**
128      * Initializes the analyzer once before any analysis is performed.
129      *
130      * @param engine a reference to the dependency-check engine
131      * @throws InitializationException if there's an error during initialization
132      */
133     @Override
prepareFileTypeAnalyzer(Engine engine)134     public void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
135         LOGGER.debug("Initializing Central analyzer");
136         LOGGER.debug("Central analyzer enabled: {}", isEnabled());
137         if (isEnabled()) {
138             try {
139                 searcher = new CentralSearch(getSettings());
140             } catch (MalformedURLException ex) {
141                 setEnabled(false);
142                 throw new InitializationException("The configured URL to Maven Central is malformed", ex);
143             }
144         }
145     }
146 
147     /**
148      * Returns the analyzer's name.
149      *
150      * @return the name of the analyzer
151      */
152     @Override
getName()153     public String getName() {
154         return ANALYZER_NAME;
155     }
156 
157     /**
158      * Returns the key used in the properties file to to reference the
159      * analyzer's enabled property.
160      *
161      * @return the analyzer's enabled property setting key.
162      */
163     @Override
getAnalyzerEnabledSettingKey()164     protected String getAnalyzerEnabledSettingKey() {
165         return Settings.KEYS.ANALYZER_CENTRAL_ENABLED;
166     }
167 
168     /**
169      * Returns the analysis phase under which the analyzer runs.
170      *
171      * @return the phase under which the analyzer runs
172      */
173     @Override
getAnalysisPhase()174     public AnalysisPhase getAnalysisPhase() {
175         return ANALYSIS_PHASE;
176     }
177 
178     /**
179      * The file filter used to determine which files this analyzer supports.
180      */
181     private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(SUPPORTED_EXTENSIONS).build();
182 
183     @Override
getFileFilter()184     protected FileFilter getFileFilter() {
185         return FILTER;
186     }
187 
188     /**
189      * Performs the analysis.
190      *
191      * @param dependency the dependency to analyze
192      * @param engine the engine
193      * @throws AnalysisException when there's an exception during analysis
194      */
195     @Override
analyzeDependency(Dependency dependency, Engine engine)196     public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
197         try {
198             final List<MavenArtifact> mas = fetchMavenArtifacts(dependency);
199             final Confidence confidence = mas.size() > 1 ? Confidence.HIGH : Confidence.HIGHEST;
200             for (MavenArtifact ma : mas) {
201                 LOGGER.debug("Central analyzer found artifact ({}) for dependency ({})", ma, dependency.getFileName());
202                 dependency.addAsEvidence("central", ma, confidence);
203                 boolean pomAnalyzed = false;
204                 for (Evidence e : dependency.getEvidence(EvidenceType.VENDOR)) {
205                     if ("pom".equals(e.getSource())) {
206                         pomAnalyzed = true;
207                         break;
208                     }
209                 }
210                 if (!pomAnalyzed && ma.getPomUrl() != null) {
211                     File pomFile = null;
212                     try {
213                         final File baseDir = getSettings().getTempDirectory();
214                         pomFile = File.createTempFile("pom", ".xml", baseDir);
215                         if (!pomFile.delete()) {
216                             LOGGER.warn("Unable to fetch pom.xml for {} from Central; "
217                                     + "this could result in undetected CPE/CVEs.", dependency.getFileName());
218                             LOGGER.debug("Unable to delete temp file");
219                         }
220                         LOGGER.debug("Downloading {}", ma.getPomUrl());
221                         final Downloader downloader = new Downloader(getSettings());
222                         downloader.fetchFile(new URL(ma.getPomUrl()), pomFile);
223                         PomUtils.analyzePOM(dependency, pomFile);
224 
225                     } catch (DownloadFailedException ex) {
226                         LOGGER.warn("Unable to download pom.xml for {} from Central; "
227                                 + "this could result in undetected CPE/CVEs.", dependency.getFileName());
228                     } finally {
229                         if (pomFile != null && pomFile.exists() && !FileUtils.deleteQuietly(pomFile)) {
230                             LOGGER.debug("Failed to delete temporary pom file {}", pomFile.toString());
231                             pomFile.deleteOnExit();
232                         }
233                     }
234                 }
235 
236             }
237         } catch (IllegalArgumentException iae) {
238             LOGGER.info("invalid sha1-hash on {}", dependency.getFileName());
239         } catch (FileNotFoundException fnfe) {
240             LOGGER.debug("Artifact not found in repository: '{}", dependency.getFileName());
241         } catch (IOException ioe) {
242             final String message = "Could not connect to Central search. Analysis failed.";
243             LOGGER.error(message, ioe);
244             throw new AnalysisException(message, ioe);
245         }
246     }
247 
248     /**
249      * Downloads the corresponding list of MavenArtifacts of the given
250      * dependency from MavenCentral.
251      * <p>
252      * As the connection to MavenCentral is known to be unreliable, we implement
253      * a simple retry logic in order to compensate for 99% of the issues.
254      *
255      * @param dependency the dependency to analyze
256      * @return the downloaded list of MavenArtifacts
257      * @throws FileNotFoundException if the specified artifact is not found
258      * @throws IOException if connecting to MavenCentral finally failed
259      */
fetchMavenArtifacts(Dependency dependency)260     protected List<MavenArtifact> fetchMavenArtifacts(Dependency dependency) throws IOException {
261         IOException lastException = null;
262         long sleepingTimeBetweenRetriesInMillis = 1000;
263         int triesLeft = NUMBER_OF_TRIES;
264         while (triesLeft-- > 0) {
265             try {
266                 return searcher.searchSha1(dependency.getSha1sum());
267             } catch (FileNotFoundException fnfe) {
268                 // retry does not make sense, just throw the exception
269                 throw fnfe;
270             } catch (IOException ioe) {
271                 LOGGER.debug("Could not connect to Central search (tries left: {}): {}",
272                         triesLeft, ioe.getMessage());
273                 lastException = ioe;
274 
275                 if (triesLeft > 0) {
276                     try {
277                         Thread.sleep(sleepingTimeBetweenRetriesInMillis);
278                         sleepingTimeBetweenRetriesInMillis *= 2;
279                     } catch (InterruptedException e) {
280                         Thread.currentThread().interrupt();
281                     }
282                 }
283             }
284         }
285 
286         final String message = "Finally failed connecting to Central search."
287                 + " Giving up after " + NUMBER_OF_TRIES + " tries.";
288         throw new IOException(message, lastException);
289     }
290 
291     /**
292      * Method used by unit tests to setup the analyzer.
293      *
294      * @param searcher the Central Search object to use.
295      */
setCentralSearch(CentralSearch searcher)296     protected void setCentralSearch(CentralSearch searcher) {
297         this.searcher = searcher;
298     }
299 }
300