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