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