1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.analyzer;
19
20 import org.apache.commons.jcs3.access.exception.CacheException;
21 import org.owasp.dependencycheck.Engine;
22 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
23 import org.owasp.dependencycheck.analyzer.exception.UnexpectedAnalysisException;
24 import org.owasp.dependencycheck.data.cache.DataCache;
25 import org.owasp.dependencycheck.data.cache.DataCacheFactory;
26 import org.owasp.dependencycheck.data.central.CentralSearch;
27 import org.owasp.dependencycheck.data.nexus.MavenArtifact;
28 import org.owasp.dependencycheck.dependency.Confidence;
29 import org.owasp.dependencycheck.dependency.Dependency;
30 import org.owasp.dependencycheck.dependency.Evidence;
31 import org.owasp.dependencycheck.dependency.EvidenceType;
32 import org.owasp.dependencycheck.exception.InitializationException;
33 import org.owasp.dependencycheck.utils.DownloadFailedException;
34 import org.owasp.dependencycheck.utils.Downloader;
35 import org.owasp.dependencycheck.utils.FileFilterBuilder;
36 import org.owasp.dependencycheck.utils.FileUtils;
37 import org.owasp.dependencycheck.utils.ForbiddenException;
38 import org.owasp.dependencycheck.utils.InvalidSettingException;
39 import org.owasp.dependencycheck.utils.ResourceNotFoundException;
40 import org.owasp.dependencycheck.utils.Settings;
41 import org.owasp.dependencycheck.utils.TooManyRequestsException;
42 import org.owasp.dependencycheck.xml.pom.Model;
43 import org.owasp.dependencycheck.xml.pom.PomUtils;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import javax.annotation.concurrent.ThreadSafe;
48 import java.io.File;
49 import java.io.FileFilter;
50 import java.io.FileNotFoundException;
51 import java.io.IOException;
52 import java.net.MalformedURLException;
53 import java.net.URL;
54 import java.text.MessageFormat;
55 import java.util.List;
56
57
58
59
60
61
62
63 @ThreadSafe
64 public class CentralAnalyzer extends AbstractFileTypeAnalyzer {
65
66
67
68
69 private static final Logger LOGGER = LoggerFactory.getLogger(CentralAnalyzer.class);
70
71
72
73
74 private static final String ANALYZER_NAME = "Central Analyzer";
75
76
77
78
79 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
80
81
82
83
84 private static final String SUPPORTED_EXTENSIONS = "jar";
85
86
87
88
89 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(SUPPORTED_EXTENSIONS).build();
90
91
92
93
94 private static final int BASE_RETRY_WAIT = 1500;
95
96
97
98
99
100 private static int numberOfRetries = 7;
101
102
103
104
105 private CentralSearch searcher;
106
107
108
109 private DataCache<Model> cache;
110
111
112
113
114
115
116 @Override
117 public synchronized void initialize(Settings settings) {
118 super.initialize(settings);
119 setEnabled(checkEnabled());
120 numberOfRetries = getSettings().getInt(Settings.KEYS.ANALYZER_CENTRAL_RETRY_COUNT, numberOfRetries);
121 if (settings.getBoolean(Settings.KEYS.ANALYZER_CENTRAL_USE_CACHE, true)) {
122 try {
123 final DataCacheFactory factory = new DataCacheFactory(settings);
124 cache = factory.getPomCache();
125 } catch (CacheException ex) {
126 settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_USE_CACHE, false);
127 LOGGER.debug("Error creating cache, disabling caching", ex);
128 }
129 }
130 }
131
132
133
134
135
136
137
138 @Override
139 public boolean supportsParallelProcessing() {
140 return getSettings().getBoolean(Settings.KEYS.ANALYZER_CENTRAL_PARALLEL_ANALYSIS, true);
141 }
142
143
144
145
146
147
148
149 private boolean checkEnabled() {
150 try {
151 return getSettings().getBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED);
152 } catch (InvalidSettingException ise) {
153 LOGGER.warn("Invalid setting. Disabling the Central analyzer");
154 }
155 return false;
156 }
157
158
159
160
161
162
163
164 @Override
165 public void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
166 LOGGER.debug("Initializing Central analyzer");
167 LOGGER.debug("Central analyzer enabled: {}", isEnabled());
168 if (isEnabled()) {
169 try {
170 searcher = new CentralSearch(getSettings());
171 } catch (MalformedURLException ex) {
172 setEnabled(false);
173 throw new InitializationException("The configured URL to Maven Central is malformed", ex);
174 }
175 }
176 }
177
178
179
180
181
182
183 @Override
184 public String getName() {
185 return ANALYZER_NAME;
186 }
187
188
189
190
191
192
193
194 @Override
195 protected String getAnalyzerEnabledSettingKey() {
196 return Settings.KEYS.ANALYZER_CENTRAL_ENABLED;
197 }
198
199
200
201
202
203
204 @Override
205 public AnalysisPhase getAnalysisPhase() {
206 return ANALYSIS_PHASE;
207 }
208
209 @Override
210 protected FileFilter getFileFilter() {
211 return FILTER;
212 }
213
214
215
216
217
218
219
220
221 @Override
222 public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
223 for (Evidence e : dependency.getEvidence(EvidenceType.VENDOR)) {
224 if ("pom".equals(e.getSource())) {
225 return;
226 }
227 }
228 try {
229 final List<MavenArtifact> mas = fetchMavenArtifacts(dependency);
230 final Confidence confidence = mas.size() > 1 ? Confidence.HIGH : Confidence.HIGHEST;
231 for (MavenArtifact ma : mas) {
232 LOGGER.debug("Central analyzer found artifact ({}) for dependency ({})", ma, dependency.getFileName());
233 dependency.addAsEvidence("central", ma, confidence);
234
235 if (ma.getPomUrl() != null) {
236 File pomFile = null;
237 try {
238 final File baseDir = getSettings().getTempDirectory();
239 pomFile = File.createTempFile("pom", ".xml", baseDir);
240 if (!pomFile.delete()) {
241 LOGGER.warn("Unable to fetch pom.xml for {} from Central; "
242 + "this could result in undetected CPE/CVEs.", dependency.getFileName());
243 LOGGER.debug("Unable to delete temp file");
244 }
245 final int maxAttempts = this.getSettings().getInt(Settings.KEYS.ANALYZER_CENTRAL_RETRY_COUNT, 3);
246 int retryCount = 0;
247 long sleepingTimeBetweenRetriesInMillis = BASE_RETRY_WAIT;
248 boolean success = false;
249 Model model = null;
250 DownloadFailedException lastException = null;
251 if (cache != null) {
252 model = cache.get(ma.getPomUrl());
253 }
254 if (model != null) {
255 success = true;
256 LOGGER.debug("Cache hit for {}", ma.getPomUrl());
257 } else {
258 LOGGER.debug("Downloading {}", ma.getPomUrl());
259 do {
260
261 try {
262 Downloader.getInstance().fetchFile(new URL(ma.getPomUrl()), pomFile);
263 success = true;
264 } catch (DownloadFailedException ex) {
265 lastException = ex;
266 try {
267 Thread.sleep(sleepingTimeBetweenRetriesInMillis);
268 } catch (InterruptedException ex1) {
269 Thread.currentThread().interrupt();
270 throw new UnexpectedAnalysisException(ex1);
271 }
272 sleepingTimeBetweenRetriesInMillis *= 2;
273 } catch (ResourceNotFoundException ex) {
274 LOGGER.debug("pom.xml does not exist in Central for {}", dependency.getFileName());
275 return;
276 }
277
278 } while (!success && retryCount++ < maxAttempts);
279 }
280 if (success) {
281 if (model == null) {
282 model = PomUtils.readPom(pomFile);
283 if (cache != null) {
284 cache.put(ma.getPomUrl(), model);
285 }
286 }
287 final boolean isMainPom = mas.size() == 1 || dependency.getActualFilePath().contains(ma.getVersion());
288 JarAnalyzer.setPomEvidence(dependency, model, null, isMainPom);
289 } else {
290 LOGGER.warn("Unable to download pom.xml for {} from Central; "
291 + "this could result in undetected CPE/CVEs.", dependency.getFileName());
292 setEnabled(false);
293 LOGGER.warn("Disabling the Central Analyzer due to repeated download failures; Central Search "
294 + "may be down see https://status.maven.org/\n Note that this could result in both false "
295 + "positives and false negatives", lastException);
296 }
297
298 } catch (AnalysisException ex) {
299 LOGGER.warn(MessageFormat.format("Unable to analyze pom.xml for {0} from Central; "
300 + "this could result in undetected CPE/CVEs.", dependency.getFileName()), ex);
301
302 } finally {
303 if (pomFile != null && pomFile.exists() && !FileUtils.delete(pomFile)) {
304 LOGGER.debug("Failed to delete temporary pom file {}", pomFile);
305 pomFile.deleteOnExit();
306 }
307 }
308 }
309 }
310 } catch (TooManyRequestsException tre) {
311 this.setEnabled(false);
312 final String message = "Connections to Central search refused. Analysis failed. Disabling Central analyzer - this " +
313 "could lead to both false positives and false negatives.";
314 LOGGER.error(message, tre);
315 throw new AnalysisException(message, tre);
316 } catch (IllegalArgumentException iae) {
317 LOGGER.info("invalid sha1-hash on {}", dependency.getFileName());
318 } catch (FileNotFoundException fnfe) {
319 LOGGER.debug("Artifact not found in repository: '{}", dependency.getFileName());
320 } catch (ForbiddenException e) {
321 this.setEnabled(false);
322 final String message = "Connection to Central search refused. This is most likely not a problem with " +
323 "Dependency-Check itself and is related to network connectivity. Please check " +
324 "https://central.sonatype.org/faq/403-error-central/.";
325 LOGGER.error(message);
326 throw new AnalysisException(message, e);
327 } catch (IOException ioe) {
328 this.setEnabled(false);
329 final String message = "Could not connect to Central search. Analysis failed; disabling Central analyzer - this " +
330 "could lead to both false positives and false negatives.";
331 LOGGER.error(message, ioe);
332 throw new AnalysisException(message, ioe);
333 }
334 }
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350 protected List<MavenArtifact> fetchMavenArtifacts(Dependency dependency) throws IOException,
351 TooManyRequestsException {
352 IOException lastException = null;
353 long sleepingTimeBetweenRetriesInMillis = BASE_RETRY_WAIT;
354 int triesLeft = numberOfRetries;
355 while (triesLeft-- > 0) {
356 try {
357 return searcher.searchSha1(dependency.getSha1sum());
358 } catch (FileNotFoundException fnfe) {
359
360 throw fnfe;
361 } catch (IOException ioe) {
362 LOGGER.debug("Could not connect to Central search (tries left: {}): {}",
363 triesLeft, ioe.getMessage());
364 lastException = ioe;
365
366 if (triesLeft > 0) {
367 try {
368 Thread.sleep(sleepingTimeBetweenRetriesInMillis);
369 } catch (InterruptedException e) {
370 Thread.currentThread().interrupt();
371 throw new UnexpectedAnalysisException(e);
372 }
373 sleepingTimeBetweenRetriesInMillis *= 2;
374 }
375 }
376 }
377
378 final String message = "Finally failed connecting to Central search."
379 + " Giving up after " + numberOfRetries + " tries.";
380 throw new IOException(message, lastException);
381 }
382
383
384
385
386
387
388 protected void setCentralSearch(CentralSearch searcher) {
389 this.searcher = searcher;
390 }
391 }