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