1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.dependency;
19
20 import com.github.packageurl.MalformedPackageURLException;
21 import com.github.packageurl.PackageURL;
22 import org.apache.commons.lang3.builder.EqualsBuilder;
23 import org.apache.commons.lang3.builder.HashCodeBuilder;
24 import org.owasp.dependencycheck.data.nexus.MavenArtifact;
25 import org.owasp.dependencycheck.utils.Checksum;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import javax.annotation.concurrent.ThreadSafe;
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.Serializable;
33 import java.security.NoSuchAlgorithmException;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Comparator;
37 import java.util.HashSet;
38 import java.util.List;
39 import java.util.Optional;
40 import java.util.Set;
41 import java.util.SortedSet;
42 import java.util.TreeSet;
43 import org.apache.commons.lang3.StringUtils;
44
45 import org.owasp.dependencycheck.analyzer.exception.UnexpectedAnalysisException;
46 import org.owasp.dependencycheck.dependency.naming.CpeIdentifier;
47 import org.owasp.dependencycheck.dependency.naming.Identifier;
48 import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
49
50
51
52
53
54
55
56
57
58 @ThreadSafe
59 public class Dependency extends EvidenceCollection implements Serializable {
60
61
62
63
64 private static final long serialVersionUID = 7388854637023297752L;
65
66
67
68 private static final Logger LOGGER = LoggerFactory.getLogger(Dependency.class);
69
70
71
72 private static final HashingFunction MD5_HASHING_FUNCTION = Checksum::getMD5Checksum;
73
74
75
76 private static final HashingFunction SHA1_HASHING_FUNCTION = Checksum::getSHA1Checksum;
77
78
79
80 private static final HashingFunction SHA256_HASHING_FUNCTION = Checksum::getSHA256Checksum;
81
82
83
84 private final Set<Identifier> softwareIdentifiers = new TreeSet<>();
85
86
87
88 private final Set<Identifier> vulnerableSoftwareIdentifiers = new TreeSet<>();
89
90
91
92 private final Set<Identifier> suppressedIdentifiers = new TreeSet<>();
93
94
95
96 private final Set<Vulnerability> suppressedVulnerabilities = new HashSet<>();
97
98
99
100 private final Set<Vulnerability> vulnerabilities = new HashSet<>();
101
102
103
104 private final SortedSet<Dependency> relatedDependencies = new TreeSet<>(Dependency.NAME_COMPARATOR);
105
106
107
108
109
110
111 private final Set<IncludedByReference> includedBy = new HashSet<>();
112
113
114
115 private final Set<String> projectReferences = new HashSet<>();
116
117
118
119 private final List<String> availableVersions = new ArrayList<>();
120
121
122
123 private String actualFilePath;
124
125
126
127 private String filePath;
128
129
130
131 private String fileName;
132
133
134
135 private String packagePath;
136
137
138
139 private String md5sum;
140
141
142
143 private String sha1sum;
144
145
146
147 private String sha256sum;
148
149
150
151 private String displayName = null;
152
153
154
155 private String description;
156
157
158
159 private String license;
160
161
162
163 private boolean isVirtual = false;
164
165
166
167
168 private String name;
169
170
171
172
173 private String version;
174
175
176
177
178
179 private String ecosystem;
180
181
182
183
184 public Dependency() {
185
186 }
187
188
189
190
191
192
193 public Dependency(File file) {
194 this(file, false);
195 }
196
197
198
199
200
201
202
203
204 public Dependency(File file, boolean isVirtual) {
205 this();
206 this.isVirtual = isVirtual;
207 this.actualFilePath = file.getAbsolutePath();
208 this.filePath = this.actualFilePath;
209 this.fileName = file.getName();
210 this.packagePath = filePath;
211 if (!isVirtual && file.isFile()) {
212 calculateChecksums(file);
213 }
214 }
215
216
217
218
219
220
221 private void calculateChecksums(File file) {
222 try {
223 this.md5sum = Checksum.getMD5Checksum(file);
224 this.sha1sum = Checksum.getSHA1Checksum(file);
225 this.sha256sum = Checksum.getSHA256Checksum(file);
226 } catch (NoSuchAlgorithmException | IOException ex) {
227 LOGGER.debug(String.format("Unable to calculate checksums on %s", file), ex);
228 }
229 }
230
231
232
233
234
235
236
237 public Dependency(boolean isVirtual) {
238 this();
239 this.isVirtual = isVirtual;
240 }
241
242
243
244
245
246
247 public String getPackagePath() {
248 return packagePath;
249 }
250
251
252
253
254
255
256 public void setPackagePath(String packagePath) {
257 this.packagePath = packagePath;
258 }
259
260
261
262
263
264
265 public String getFileName() {
266 return this.fileName;
267 }
268
269
270
271
272
273
274 public void setFileName(String fileName) {
275 this.fileName = fileName;
276 }
277
278
279
280
281
282
283 public String getActualFilePath() {
284 return this.actualFilePath;
285 }
286
287
288
289
290
291
292 public void setActualFilePath(String actualFilePath) {
293 this.actualFilePath = actualFilePath;
294 this.sha1sum = null;
295 this.sha256sum = null;
296 this.md5sum = null;
297 final File file = getActualFile();
298 if (file.isFile()) {
299 calculateChecksums(this.getActualFile());
300 }
301 }
302
303
304
305
306
307
308 public File getActualFile() {
309 return new File(this.actualFilePath);
310 }
311
312
313
314
315
316
317
318
319 public String getDisplayFileName() {
320 if (displayName != null) {
321 return displayName;
322 }
323 if (!isVirtual) {
324 return fileName;
325 }
326 if (name == null) {
327 return fileName;
328 }
329 if (version == null) {
330 return name;
331 }
332 return name + ":" + version;
333 }
334
335
336
337
338
339
340 public void setDisplayFileName(String displayName) {
341 this.displayName = displayName;
342 }
343
344
345
346
347
348
349
350
351
352
353
354 public String getFilePath() {
355 return this.filePath;
356 }
357
358
359
360
361
362
363 public void setFilePath(String filePath) {
364 this.filePath = filePath;
365 }
366
367
368
369
370
371
372 public String getMd5sum() {
373 if (md5sum == null) {
374 this.md5sum = determineHashes(MD5_HASHING_FUNCTION);
375 }
376
377 return this.md5sum;
378 }
379
380
381
382
383
384
385 public void setMd5sum(String md5sum) {
386 this.md5sum = md5sum;
387 }
388
389
390
391
392
393
394 public String getSha1sum() {
395 if (sha1sum == null) {
396 this.sha1sum = determineHashes(SHA1_HASHING_FUNCTION);
397 }
398 return this.sha1sum;
399 }
400
401
402
403
404
405
406 public void setSha1sum(String sha1sum) {
407 this.sha1sum = sha1sum;
408 }
409
410
411
412
413
414
415 public String getSha256sum() {
416 if (sha256sum == null) {
417 this.sha256sum = determineHashes(SHA256_HASHING_FUNCTION);
418 }
419 return sha256sum;
420 }
421
422 public void setSha256sum(String sha256sum) {
423 this.sha256sum = sha256sum;
424 }
425
426
427
428
429
430
431 public synchronized Set<Identifier> getSoftwareIdentifiers() {
432 return Collections.unmodifiableSet(softwareIdentifiers);
433 }
434
435
436
437
438
439
440 public synchronized Set<Identifier> getVulnerableSoftwareIdentifiers() {
441 return Collections.unmodifiableSet(this.vulnerableSoftwareIdentifiers);
442 }
443
444
445
446
447
448
449 public synchronized int getVulnerableSoftwareIdentifiersCount() {
450 return this.vulnerableSoftwareIdentifiers.size();
451 }
452
453
454
455
456
457
458
459 public synchronized boolean hasKnownExploitedVulnerability() {
460 for (Vulnerability v : vulnerabilities) {
461 if (v.getKnownExploitedVulnerability() != null) {
462 return true;
463 }
464 }
465 return false;
466 }
467
468
469
470
471
472
473
474 protected synchronized void addSoftwareIdentifiers(Set<Identifier> identifiers) {
475 this.softwareIdentifiers.addAll(identifiers);
476 }
477
478
479
480
481
482
483
484 protected synchronized void addVulnerableSoftwareIdentifiers(Set<Identifier> identifiers) {
485 this.vulnerableSoftwareIdentifiers.addAll(identifiers);
486 }
487
488
489
490
491
492
493
494 public synchronized void addSoftwareIdentifier(Identifier identifier) {
495
496 assert !(identifier instanceof CpeIdentifier) : "vulnerability identifier cannot be added to software identifiers";
497
498 final Optional<Identifier> found = softwareIdentifiers.stream().filter(id
499 -> id.getValue().equals(identifier.getValue())).findFirst();
500 if (found.isPresent()) {
501
502 final Identifier existing = found.get();
503 if (existing.getConfidence().compareTo(identifier.getConfidence()) < 0) {
504 existing.setConfidence(identifier.getConfidence());
505 }
506 if (existing.getNotes() != null && identifier.getNotes() != null) {
507 existing.setNotes(existing.getNotes() + " " + identifier.getNotes());
508 } else if (identifier.getNotes() != null) {
509 existing.setNotes(identifier.getNotes());
510 }
511 if (existing.getUrl() == null && identifier.getUrl() != null) {
512 existing.setUrl(identifier.getUrl());
513 }
514 } else {
515 this.softwareIdentifiers.add(identifier);
516 }
517 }
518
519
520
521
522
523
524
525 public synchronized void addVulnerableSoftwareIdentifier(Identifier identifier) {
526 this.vulnerableSoftwareIdentifiers.add(identifier);
527 }
528
529
530
531
532
533
534 public synchronized void removeVulnerableSoftwareIdentifier(Identifier i) {
535 this.vulnerableSoftwareIdentifiers.remove(i);
536 }
537
538
539
540
541
542
543
544
545 public void addAsEvidence(String source, MavenArtifact mavenArtifact, Confidence confidence) {
546 if (mavenArtifact.getGroupId() != null && !mavenArtifact.getGroupId().isEmpty()) {
547 this.addEvidence(EvidenceType.VENDOR, source, "groupid", mavenArtifact.getGroupId(), confidence);
548 }
549 if (mavenArtifact.getArtifactId() != null && !mavenArtifact.getArtifactId().isEmpty()) {
550 this.addEvidence(EvidenceType.PRODUCT, source, "artifactid", mavenArtifact.getArtifactId(), confidence);
551 this.addEvidence(EvidenceType.VENDOR, source, "artifactid", mavenArtifact.getArtifactId(), confidence);
552 }
553 if (mavenArtifact.getVersion() != null && !mavenArtifact.getVersion().isEmpty()) {
554 this.addEvidence(EvidenceType.VERSION, source, "version", mavenArtifact.getVersion(), confidence);
555 }
556 boolean found = false;
557 if (mavenArtifact.getArtifactUrl() != null && !mavenArtifact.getArtifactUrl().isEmpty()) {
558 synchronized (this) {
559 for (Identifier i : this.softwareIdentifiers) {
560 if (i instanceof PurlIdentifier) {
561 final PurlIdentifier id = (PurlIdentifier) i;
562 if (mavenArtifact.getArtifactId().equals(id.getName())
563 && mavenArtifact.getGroupId().equals(id.getNamespace())) {
564 found = true;
565 i.setConfidence(Confidence.HIGHEST);
566 final String url = "https://search.maven.org/search?q=1:" + this.getSha1sum();
567 i.setUrl(url);
568
569 LOGGER.debug("Already found identifier {}. Confidence set to highest", i.getValue());
570 break;
571 }
572 }
573 }
574 }
575 }
576 if (!found && !StringUtils.isAnyEmpty(mavenArtifact.getGroupId(),
577 mavenArtifact.getArtifactId(), mavenArtifact.getVersion())) {
578 try {
579 LOGGER.debug("Adding new maven identifier {}", mavenArtifact);
580 final PackageURL p = new PackageURL("maven", mavenArtifact.getGroupId(),
581 mavenArtifact.getArtifactId(), mavenArtifact.getVersion(), null, null);
582 final PurlIdentifier id = new PurlIdentifier(p, Confidence.HIGHEST);
583 this.addSoftwareIdentifier(id);
584 } catch (MalformedPackageURLException ex) {
585 throw new UnexpectedAnalysisException(ex);
586 }
587 }
588 }
589
590
591
592
593
594
595 public synchronized Set<Identifier> getSuppressedIdentifiers() {
596 return Collections.unmodifiableSet(this.suppressedIdentifiers);
597 }
598
599
600
601
602
603
604 public synchronized void addSuppressedIdentifier(Identifier identifier) {
605 this.suppressedIdentifiers.add(identifier);
606 }
607
608
609
610
611
612
613 public synchronized Set<Vulnerability> getVulnerabilities() {
614 return getVulnerabilities(false);
615 }
616
617
618
619
620
621
622
623 public synchronized Set<Vulnerability> getVulnerabilities(boolean sorted) {
624 final Set<Vulnerability> vulnerabilitySet;
625 if (sorted) {
626 vulnerabilitySet = new TreeSet<>(vulnerabilities);
627 } else {
628 vulnerabilitySet = vulnerabilities;
629 }
630 return Collections.unmodifiableSet(vulnerabilitySet);
631 }
632
633
634
635
636
637
638 public synchronized int getVulnerabilitiesCount() {
639 return vulnerabilities.size();
640 }
641
642
643
644
645
646
647 public synchronized Set<Vulnerability> getSuppressedVulnerabilities() {
648 return getSuppressedVulnerabilities(false);
649 }
650
651
652
653
654
655
656
657 public synchronized Set<Vulnerability> getSuppressedVulnerabilities(boolean sorted) {
658 final Set<Vulnerability> vulnerabilitySet;
659 if (sorted) {
660 vulnerabilitySet = new TreeSet<>(suppressedVulnerabilities);
661 } else {
662 vulnerabilitySet = suppressedVulnerabilities;
663 }
664 return Collections.unmodifiableSet(vulnerabilitySet);
665 }
666
667
668
669
670
671
672 public synchronized void addSuppressedVulnerability(Vulnerability vulnerability) {
673 this.suppressedVulnerabilities.add(vulnerability);
674 }
675
676
677
678
679
680
681 public String getDescription() {
682 return description;
683 }
684
685
686
687
688
689
690 public void setDescription(String description) {
691 this.description = description;
692 }
693
694
695
696
697
698
699 public String getLicense() {
700 return license;
701 }
702
703
704
705
706
707
708 public void setLicense(String license) {
709 this.license = license;
710 }
711
712
713
714
715 public String getName() {
716 return name;
717 }
718
719
720
721
722 public void setName(String name) {
723 this.name = name;
724 }
725
726
727
728
729
730
731
732 private String determineHashes(HashingFunction hashFunction) {
733 if (isVirtual) {
734 return null;
735 }
736 try {
737 final File file = getActualFile();
738 return hashFunction.hash(file);
739 } catch (IOException | RuntimeException ex) {
740 LOGGER.warn("Unable to read '{}' to determine hashes.", actualFilePath);
741 LOGGER.debug("", ex);
742 } catch (NoSuchAlgorithmException ex) {
743 LOGGER.warn("Unable to use MD5 or SHA1 checksums.");
744 LOGGER.debug("", ex);
745 }
746 return null;
747 }
748
749
750
751
752
753
754 public synchronized void addVulnerability(Vulnerability vulnerability) {
755 this.vulnerabilities.add(vulnerability);
756 }
757
758
759
760
761
762
763 public synchronized void addVulnerabilities(List<Vulnerability> vulnerabilities) {
764 this.vulnerabilities.addAll(vulnerabilities);
765 }
766
767
768
769
770
771
772 public synchronized void removeVulnerability(Vulnerability v) {
773 this.vulnerabilities.remove(v);
774 }
775
776
777
778
779
780
781
782
783 public synchronized Set<Dependency> getRelatedDependencies() {
784 return Collections.unmodifiableSet(relatedDependencies);
785 }
786
787
788
789
790 public synchronized void clearRelatedDependencies() {
791 relatedDependencies.clear();
792 }
793
794
795
796
797
798
799
800 public synchronized Set<IncludedByReference> getIncludedBy() {
801 return Collections.unmodifiableSet(new HashSet<>(includedBy));
802 }
803
804
805
806
807
808
809
810 public synchronized void addIncludedBy(String includedBy) {
811 this.includedBy.add(new IncludedByReference(includedBy, null));
812 }
813
814
815
816
817
818
819
820
821 public synchronized void addIncludedBy(String includedBy, String type) {
822 this.includedBy.add(new IncludedByReference(includedBy, type));
823 }
824
825
826
827
828
829
830 public synchronized void addAllIncludedBy(Set<IncludedByReference> includedBy) {
831 this.includedBy.addAll(includedBy);
832 }
833
834
835
836
837
838
839 public synchronized Set<String> getProjectReferences() {
840 return Collections.unmodifiableSet(new HashSet<>(projectReferences));
841 }
842
843
844
845
846
847
848 public synchronized void addProjectReference(String projectReference) {
849 this.projectReferences.add(projectReference);
850 }
851
852
853
854
855
856
857 public synchronized void addAllProjectReferences(Set<String> projectReferences) {
858 this.projectReferences.addAll(projectReferences);
859 }
860
861
862
863
864
865
866 @SuppressWarnings("ReferenceEquality")
867 public synchronized void addRelatedDependency(Dependency dependency) {
868 if (this == dependency) {
869 LOGGER.warn("Attempted to add a circular reference - please post the log file to issue #172 here "
870 + "https://github.com/jeremylong/DependencyCheck/issues/172");
871 LOGGER.debug("this: {}", this);
872 LOGGER.debug("dependency: {}", dependency);
873 } else if (NAME_COMPARATOR.compare(this, dependency) == 0) {
874 LOGGER.debug("Attempted to add the same dependency as this, likely due to merging identical dependencies "
875 + "obtained from different modules");
876 LOGGER.debug("this: {}", this);
877 LOGGER.debug("dependency: {}", dependency);
878 } else if (!relatedDependencies.add(dependency)) {
879 LOGGER.debug("Failed to add dependency, likely due to referencing the same file as another dependency in the set.");
880 LOGGER.debug("this: {}", this);
881 LOGGER.debug("dependency: {}", dependency);
882 }
883 }
884
885
886
887
888
889
890 public synchronized void removeRelatedDependencies(Dependency dependency) {
891 this.relatedDependencies.remove(dependency);
892 }
893
894
895
896
897
898
899 public synchronized List<String> getAvailableVersions() {
900 return Collections.unmodifiableList(new ArrayList<>(availableVersions));
901 }
902
903
904
905
906
907
908 public synchronized void addAvailableVersion(String version) {
909 this.availableVersions.add(version);
910 }
911
912
913
914
915
916
917
918 public boolean isVirtual() {
919 return isVirtual;
920 }
921
922
923
924
925
926
927
928 @Override
929 public boolean equals(Object obj) {
930 if (obj == null || !(obj instanceof Dependency)) {
931 return false;
932 }
933 if (this == obj) {
934 return true;
935 }
936 final Dependency other = (Dependency) obj;
937 return new EqualsBuilder()
938 .appendSuper(super.equals(obj))
939 .append(this.actualFilePath, other.actualFilePath)
940 .append(this.filePath, other.filePath)
941 .append(this.fileName, other.fileName)
942 .append(this.packagePath, other.packagePath)
943 .append(this.md5sum, other.md5sum)
944 .append(this.sha1sum, other.sha1sum)
945 .append(this.sha256sum, other.sha256sum)
946 .append(this.softwareIdentifiers, other.softwareIdentifiers)
947 .append(this.vulnerableSoftwareIdentifiers, other.vulnerableSoftwareIdentifiers)
948 .append(this.suppressedIdentifiers, other.suppressedIdentifiers)
949 .append(this.description, other.description)
950 .append(this.license, other.license)
951 .append(this.vulnerabilities, other.vulnerabilities)
952 .append(this.projectReferences, other.projectReferences)
953 .append(this.availableVersions, other.availableVersions)
954 .append(this.version, other.version)
955 .append(this.ecosystem, other.ecosystem)
956 .isEquals();
957 }
958
959
960
961
962
963
964 @Override
965 public int hashCode() {
966 return new HashCodeBuilder(3, 47)
967 .appendSuper(super.hashCode())
968 .append(actualFilePath)
969 .append(filePath)
970 .append(fileName)
971 .append(packagePath)
972 .append(md5sum)
973 .append(sha1sum)
974 .append(sha256sum)
975 .append(softwareIdentifiers)
976 .append(vulnerableSoftwareIdentifiers)
977 .append(suppressedIdentifiers)
978 .append(description)
979 .append(license)
980 .append(vulnerabilities)
981 .append(projectReferences)
982 .append(availableVersions)
983 .append(version)
984 .append(ecosystem)
985 .toHashCode();
986 }
987
988
989
990
991
992
993
994 @Override
995 public synchronized String toString() {
996 return "Dependency{ fileName='" + fileName + "', actualFilePath='" + actualFilePath
997 + "', filePath='" + filePath + "', packagePath='" + packagePath + "'}";
998 }
999
1000
1001
1002
1003
1004
1005 public synchronized void addSuppressedVulnerabilities(List<Vulnerability> vulns) {
1006 this.suppressedVulnerabilities.addAll(vulns);
1007 }
1008
1009
1010
1011
1012 public String getVersion() {
1013 return version;
1014 }
1015
1016
1017
1018
1019 public void setVersion(String version) {
1020 this.version = version;
1021 }
1022
1023
1024
1025
1026 public String getEcosystem() {
1027 return ecosystem;
1028 }
1029
1030
1031
1032
1033 public void setEcosystem(String ecosystem) {
1034 this.ecosystem = ecosystem;
1035 }
1036
1037
1038
1039
1040
1041 public static final Comparator<Dependency> NAME_COMPARATOR
1042 = Comparator.comparing((Dependency d) -> (d.getDisplayFileName() + d.getFilePath()));
1043
1044
1045
1046
1047
1048 interface HashingFunction {
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058 String hash(File file) throws IOException, NoSuchAlgorithmException;
1059 }
1060 }