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 com.fasterxml.jackson.annotation.JsonProperty;
21 import com.fasterxml.jackson.databind.DeserializationFeature;
22 import com.fasterxml.jackson.databind.JsonNode;
23 import com.fasterxml.jackson.databind.ObjectMapper;
24 import com.fasterxml.jackson.databind.ObjectReader;
25 import com.github.packageurl.MalformedPackageURLException;
26 import com.github.packageurl.PackageURL;
27 import com.github.packageurl.PackageURLBuilder;
28 import java.util.Map;
29 import java.util.stream.Collectors;
30 import org.owasp.dependencycheck.Engine;
31 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
32 import org.owasp.dependencycheck.data.nvd.ecosystem.Ecosystem;
33 import org.owasp.dependencycheck.dependency.Confidence;
34 import org.owasp.dependencycheck.dependency.Dependency;
35 import org.owasp.dependencycheck.dependency.EvidenceType;
36 import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
37 import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
38 import org.owasp.dependencycheck.utils.Settings;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import javax.annotation.concurrent.ThreadSafe;
43 import java.io.File;
44 import java.io.FileFilter;
45 import java.io.IOException;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.regex.Pattern;
50
51
52
53
54
55
56
57
58
59 @Experimental
60 @ThreadSafe
61 public class PinnedMavenInstallAnalyzer extends AbstractFileTypeAnalyzer {
62
63
64
65
66 private static final Logger LOGGER = LoggerFactory.getLogger(PinnedMavenInstallAnalyzer.class);
67
68
69
70
71 private static final String ANALYZER_NAME = "Pinned Maven install Analyzer";
72
73
74
75
76 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
77
78
79
80
81
82
83
84
85
86
87 private static final Pattern MAVEN_INSTALL_JSON_PATTERN = Pattern.compile("(.+install.*|.*install.+)\\.json");
88
89
90
91
92 private static final FileFilter FILTER = (File file) -> MAVEN_INSTALL_JSON_PATTERN.matcher(file.getName()).matches();
93
94 @Override
95 protected FileFilter getFileFilter() {
96 return FILTER;
97 }
98
99 @Override
100 public String getName() {
101 return ANALYZER_NAME;
102 }
103
104 @Override
105 public AnalysisPhase getAnalysisPhase() {
106 return ANALYSIS_PHASE;
107 }
108
109 @Override
110 protected String getAnalyzerEnabledSettingKey() {
111 return Settings.KEYS.ANALYZER_MAVEN_INSTALL_ENABLED;
112 }
113
114 @Override
115 protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
116 LOGGER.debug("Checking file {}", dependency.getActualFilePath());
117
118 final File dependencyFile = dependency.getActualFile();
119 if (!dependencyFile.isFile() || dependencyFile.length() == 0) {
120 return;
121 }
122
123 final DependencyTree tree;
124 List<MavenDependency> deps;
125 try {
126 JsonNode jsonNode = MAPPER.readTree(dependencyFile);
127 JsonNode v2Version = jsonNode.path("version");
128 JsonNode v010Version = jsonNode.path("dependency_tree").path("version");
129
130 if (v2Version.isTextual()) {
131 final InstallFileV2 installFile = INSTALL_FILE_V2_READER.readValue(dependencyFile);
132 if (!Objects.equals(installFile.getAutogeneratedSentinel(), "THERE_IS_NO_DATA_ONLY_ZUUL")) {
133 return;
134 }
135 if (!Objects.equals(installFile.getVersion(), "2")) {
136 LOGGER.warn("Unsupported pinned maven_install.json version {}. Continuing optimistically.", installFile.getVersion());
137 }
138 deps = installFile.getArtifacts().entrySet().stream().map(entry -> new MavenDependency(
139 entry.getKey() + ":" + entry.getValue().getVersion()
140 )).collect(Collectors.toList());
141 } else if (v010Version.isTextual()) {
142 final InstallFile installFile = INSTALL_FILE_READER.readValue(dependencyFile);
143 tree = installFile.getDependencyTree();
144 if (tree == null) {
145 return;
146 } else if (!Objects.equals(tree.getAutogeneratedSentinel(), "THERE_IS_NO_DATA_ONLY_ZUUL")) {
147 return;
148 }
149 if (!Objects.equals(tree.getVersion(), "0.1.0")) {
150 LOGGER.warn("Unsupported pinned maven_install.json version {}. Continuing optimistically.", tree.getVersion());
151 }
152 deps = tree.getDependencies();
153 } else {
154 LOGGER.warn("No pinned maven_install.json version found. Cannot Parse");
155 return;
156 }
157
158
159 } catch (IOException e) {
160 System.out.println("e");
161 return;
162 }
163
164 engine.removeDependency(dependency);
165
166 if (deps == null) {
167 deps = Collections.emptyList();
168 }
169
170 for (MavenDependency dep : deps) {
171 if (dep.getCoord() == null) {
172 LOGGER.warn("Unexpected null coordinate in {}", dependency.getActualFilePath());
173 continue;
174 }
175
176 LOGGER.debug("Analyzing {}", dep.getCoord());
177 final String[] pieces = dep.getCoord().split(":");
178 if (pieces.length < 3 || pieces.length > 5) {
179 LOGGER.warn("Invalid maven coordinate {}", dep.getCoord());
180 continue;
181 }
182
183 final String group = pieces[0];
184 final String artifact = pieces[1];
185 final String version;
186 String classifier = null;
187 switch (pieces.length) {
188 case 3:
189 version = pieces[2];
190 break;
191 case 4:
192 classifier = pieces[2];
193 version = pieces[3];
194 break;
195 default:
196
197 classifier = pieces[3];
198 version = pieces[4];
199 break;
200 }
201
202 if ("sources".equals(classifier) || "javadoc".equals(classifier)) {
203 LOGGER.debug("Skipping sources jar {}", dep.getCoord());
204 continue;
205 }
206
207 final Dependency d = new Dependency(dependency.getActualFile(), true);
208 d.setEcosystem(Ecosystem.JAVA);
209 d.addEvidence(EvidenceType.VENDOR, "project", "groupid", group, Confidence.HIGHEST);
210 d.addEvidence(EvidenceType.PRODUCT, "project", "artifactid", artifact, Confidence.HIGHEST);
211 d.addEvidence(EvidenceType.VERSION, "project", "version", version, Confidence.HIGHEST);
212 d.setName(String.format("%s:%s", group, artifact));
213 d.setFilePath(String.format("%s>>%s", dependency.getActualFile(), dep.getCoord()));
214 d.setFileName(dep.getCoord());
215 try {
216 final PackageURLBuilder purl = PackageURLBuilder.aPackageURL()
217 .withType(PackageURL.StandardTypes.MAVEN)
218 .withNamespace(group)
219 .withName(artifact)
220 .withVersion(version);
221 if (classifier != null) {
222 purl.withQualifier("classifier", classifier);
223 }
224 d.addSoftwareIdentifier(new PurlIdentifier(purl.build(), Confidence.HIGHEST));
225 } catch (MalformedPackageURLException e) {
226 d.addSoftwareIdentifier(new GenericIdentifier("maven_install JSON coord " + dep.getCoord(), Confidence.HIGH));
227 }
228 d.setVersion(version);
229 engine.addDependency(d);
230 }
231 }
232
233 @Override
234 protected void prepareFileTypeAnalyzer(Engine engine) {
235
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249 private static class InstallFile {
250
251
252
253
254 @JsonProperty("dependency_tree")
255 private DependencyTree dependencyTree;
256
257
258
259
260
261
262 public DependencyTree getDependencyTree() {
263 return dependencyTree;
264 }
265 }
266
267
268
269
270
271 private static class DependencyTree {
272
273
274
275
276
277 @JsonProperty("__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY")
278 private String autogeneratedSentinel;
279
280
281
282
283
284
285 @JsonProperty("dependencies")
286 private List<MavenDependency> dependencies;
287
288
289
290
291 @JsonProperty("version")
292 private String version;
293
294
295
296
297
298
299 public String getAutogeneratedSentinel() {
300 return autogeneratedSentinel;
301 }
302
303
304
305
306
307
308 public List<MavenDependency> getDependencies() {
309 return dependencies;
310 }
311
312
313
314
315
316
317 public String getVersion() {
318 return version;
319 }
320
321 }
322
323
324
325
326
327 private static class MavenDependency {
328 public MavenDependency(String coord) {
329 this.coord = coord;
330 }
331
332 public MavenDependency() {
333 }
334
335
336
337
338 @JsonProperty("coord")
339 private String coord;
340
341
342
343
344
345
346 public String getCoord() {
347 return coord;
348 }
349 }
350
351
352
353
354 private static final ObjectReader INSTALL_FILE_READER;
355 private static final ObjectReader INSTALL_FILE_V2_READER;
356 private static final ObjectMapper MAPPER;
357
358 static {
359 MAPPER = new ObjectMapper();
360 MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
361 INSTALL_FILE_READER = MAPPER.readerFor(InstallFile.class);
362 INSTALL_FILE_V2_READER = MAPPER.readerFor(InstallFileV2.class);
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376 private static class InstallFileV2 {
377
378
379
380
381 @JsonProperty("version")
382 private String version;
383
384
385
386
387
388
389
390
391
392
393 @JsonProperty("artifacts")
394 private Map<String, Artifactv2> artifacts;
395
396
397
398
399
400 @JsonProperty("__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY")
401 private String autogeneratedSentinel;
402
403
404
405
406
407
408 public Map<String, Artifactv2> getArtifacts() {
409 return artifacts;
410 }
411
412
413
414
415
416
417 public String getVersion() {
418 return version;
419 }
420
421
422
423
424
425
426 public String getAutogeneratedSentinel() {
427 return autogeneratedSentinel;
428 }
429 }
430 private static class Artifactv2 {
431
432
433
434
435 @JsonProperty("version")
436 private String version;
437
438
439
440
441
442
443 public String getVersion() {
444 return version;
445 }
446 }
447
448
449 }