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 if (cache != null) {
251 model = cache.get(ma.getPomUrl());
252 }
253 if (model != null) {
254 success = true;
255 LOGGER.debug("Cache hit for {}", ma.getPomUrl());
256 } else {
257 LOGGER.debug("Downloading {}", ma.getPomUrl());
258 do {
259
260 try {
261 Downloader.getInstance().fetchFile(new URL(ma.getPomUrl()), pomFile);
262 success = true;
263 } catch (DownloadFailedException ex) {
264 try {
265 Thread.sleep(sleepingTimeBetweenRetriesInMillis);
266 } catch (InterruptedException ex1) {
267 Thread.currentThread().interrupt();
268 throw new UnexpectedAnalysisException(ex1);
269 }
270 sleepingTimeBetweenRetriesInMillis *= 2;
271 } catch (ResourceNotFoundException ex) {
272 LOGGER.debug("pom.xml does not exist in Central for {}", dependency.getFileName());
273 return;
274 }
275
276 } while (!success && retryCount++ < maxAttempts);
277 }
278 if (success) {
279 if (model == null) {
280 model = PomUtils.readPom(pomFile);
281 if (cache != null) {
282 cache.put(ma.getPomUrl(), model);
283 }
284 }
285 final boolean isMainPom = mas.size() == 1 || dependency.getActualFilePath().contains(ma.getVersion());
286 JarAnalyzer.setPomEvidence(dependency, model, null, isMainPom);
287 } else {
288 LOGGER.warn("Unable to download pom.xml for {} from Central; "
289 + "this could result in undetected CPE/CVEs.", dependency.getFileName());
290 }
291
292 } catch (AnalysisException ex) {
293 LOGGER.warn(MessageFormat.format("Unable to analyze pom.xml for {0} from Central; "
294 + "this could result in undetected CPE/CVEs.", dependency.getFileName()), ex);
295
296 } finally {
297 if (pomFile != null && pomFile.exists() && !FileUtils.delete(pomFile)) {
298 LOGGER.debug("Failed to delete temporary pom file {}", pomFile);
299 pomFile.deleteOnExit();
300 }
301 }
302 }
303 }
304 } catch (TooManyRequestsException tre) {
305 this.setEnabled(false);
306 final String message = "Connections to Central search refused. Analysis failed.";
307 LOGGER.error(message, tre);
308 throw new AnalysisException(message, tre);
309 } catch (IllegalArgumentException iae) {
310 LOGGER.info("invalid sha1-hash on {}", dependency.getFileName());
311 } catch (FileNotFoundException fnfe) {
312 LOGGER.debug("Artifact not found in repository: '{}", dependency.getFileName());
313 } catch (ForbiddenException e) {
314 final String message = "Connection to Central search refused. This is most likely not a problem with " +
315 "Dependency-Check itself and is related to network connectivity. Please check " +
316 "https://central.sonatype.org/faq/403-error-central/.";
317 LOGGER.error(message);
318 throw new AnalysisException(message, e);
319 } catch (IOException ioe) {
320 final String message = "Could not connect to Central search. Analysis failed.";
321 LOGGER.error(message, ioe);
322 throw new AnalysisException(message, ioe);
323 }
324 }
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340 protected List<MavenArtifact> fetchMavenArtifacts(Dependency dependency) throws IOException,
341 TooManyRequestsException {
342 IOException lastException = null;
343 long sleepingTimeBetweenRetriesInMillis = BASE_RETRY_WAIT;
344 int triesLeft = numberOfRetries;
345 while (triesLeft-- > 0) {
346 try {
347 return searcher.searchSha1(dependency.getSha1sum());
348 } catch (FileNotFoundException fnfe) {
349
350 throw fnfe;
351 } catch (IOException ioe) {
352 LOGGER.debug("Could not connect to Central search (tries left: {}): {}",
353 triesLeft, ioe.getMessage());
354 lastException = ioe;
355
356 if (triesLeft > 0) {
357 try {
358 Thread.sleep(sleepingTimeBetweenRetriesInMillis);
359 } catch (InterruptedException e) {
360 Thread.currentThread().interrupt();
361 throw new UnexpectedAnalysisException(e);
362 }
363 sleepingTimeBetweenRetriesInMillis *= 2;
364 }
365 }
366 }
367
368 final String message = "Finally failed connecting to Central search."
369 + " Giving up after " + numberOfRetries + " tries.";
370 throw new IOException(message, lastException);
371 }
372
373
374
375
376
377
378 protected void setCentralSearch(CentralSearch searcher) {
379 this.searcher = searcher;
380 }
381 }