2222import org .apache .commons .codec .digest .DigestUtils ;
2323import org .cyclonedx .Version ;
2424import org .cyclonedx .model .Hash ;
25+ import org .cyclonedx .model .VersionFilter ;
26+
2527import java .io .BufferedInputStream ;
2628import java .io .File ;
2729import java .io .IOException ;
3234import java .net .URL ;
3335import java .nio .file .Files ;
3436import java .security .MessageDigest ;
37+ import java .security .NoSuchAlgorithmException ;
3538import java .util .Arrays ;
3639import java .util .LinkedList ;
3740import java .util .List ;
@@ -53,40 +56,112 @@ private BomUtils() {
5356 * @since 1.0.0
5457 */
5558 public static List <Hash > calculateHashes (final File file , final Version schemaVersion ) throws IOException {
59+ final List <Hash .Algorithm > algorithms = new LinkedList <>(Arrays .asList (
60+ Hash .Algorithm .MD5 ,
61+ Hash .Algorithm .SHA1 ,
62+ Hash .Algorithm .SHA_256 ,
63+ Hash .Algorithm .SHA_512 ,
64+ Hash .Algorithm .SHA3_256 ,
65+ Hash .Algorithm .SHA3_512
66+ ));
67+ if (schemaVersion .getVersion () >= 1.2 ) {
68+ algorithms .add (Hash .Algorithm .SHA_384 );
69+ algorithms .add (Hash .Algorithm .SHA3_384 );
70+ }
71+ return calculateHashes (file , schemaVersion , algorithms );
72+ }
73+
74+ /**
75+ * Calculates the hashes of the specified file using only the specified algorithms.
76+ * @param file the File to calculate hashes on
77+ * @param schemaVersion enum denoting the schema version in use. Affects hash algorithm selection.
78+ * @param algorithms the list of algorithms to calculate hashes for
79+ * @return a List of Hash objects
80+ * @throws IOException an IOException
81+ * @throws IllegalArgumentException if an algorithm is not supported by the schema version
82+ * @since 9.2.0
83+ */
84+ public static List <Hash > calculateHashes (final File file , final Version schemaVersion ,
85+ final List <Hash .Algorithm > algorithms ) throws IOException {
5686 if (file == null || !file .exists () || !file .canRead () || !file .isFile ()) {
5787 return null ;
5888 }
59- long start = System .currentTimeMillis ();
89+ if (algorithms == null || algorithms .isEmpty ()) {
90+ return new LinkedList <>();
91+ }
92+
6093 final List <Hash > hashes = new LinkedList <>();
61- final List <MessageDigest > digests = new LinkedList <>(Arrays .asList (DigestUtils .getMd5Digest ()
62- , DigestUtils .getSha1Digest ()
63- , DigestUtils .getSha256Digest () , DigestUtils .getSha512Digest ()));
64- if (schemaVersion .getVersion () >= 1.2 ) {
65- digests .add (DigestUtils .getSha384Digest ());
66- try {
67- digests .add (DigestUtils .getSha3_384Digest ());
68- } catch (Exception | NoSuchMethodError e ) { /* Not available in Java 8 and only available in later versions of DigestUtils */ }
94+ final List <MessageDigest > digests = new LinkedList <>();
6995
96+ for (Hash .Algorithm algorithm : algorithms ) {
97+ validateAlgorithmForVersion (algorithm , schemaVersion );
98+ MessageDigest digest = getDigestForAlgorithm (algorithm );
99+ if (digest != null ) {
100+ digests .add (digest );
101+ }
70102 }
71- try {
72- digests .add (DigestUtils .getSha3_256Digest ());
73- } catch (Exception | NoSuchMethodError e ) { /* Not available in Java 8 and only available in later versions of DigestUtils */ }
74- try {
75- digests .add (DigestUtils .getSha3_512Digest ());
76- } catch (Exception | NoSuchMethodError e ) { /* Not available in Java 8 and only available in later versions of DigestUtils */ }
77103
78104 final int bufSize = 8192 ;
79- try (InputStream fis = new BufferedInputStream (Files .newInputStream (file .toPath ()),bufSize )) {
105+ try (InputStream fis = new BufferedInputStream (Files .newInputStream (file .toPath ()), bufSize )) {
80106 final byte [] buf = new byte [bufSize ];
81107 while (fis .available () > 0 ) {
82108 final int read = fis .read (buf );
83- digests .stream ().parallel ().forEach (d -> d .update (buf , 0 , read ));
109+ digests .stream ().parallel ().forEach (d -> d .update (buf , 0 , read ));
84110 }
85111 }
86- digests .stream ().map (d -> new Hash (toAlgorithm (d ), Hex .encodeHexString (d .digest ()))).forEach (hashes ::add );
112+ digests .stream ().map (d -> new Hash (toAlgorithm (d ), Hex .encodeHexString (d .digest ()))).forEach (hashes ::add );
87113 return hashes ;
88114 }
89115
116+ private static void validateAlgorithmForVersion (Hash .Algorithm algorithm , Version schemaVersion ) {
117+ try {
118+ java .lang .reflect .Field field = Hash .Algorithm .class .getField (algorithm .name ());
119+ VersionFilter versionFilter = field .getAnnotation (VersionFilter .class );
120+ if (versionFilter != null ) {
121+ Version minVersion = versionFilter .value ();
122+ if (schemaVersion .getVersion () < minVersion .getVersion ()) {
123+ throw new IllegalArgumentException (
124+ "Algorithm " + algorithm .getSpec () + " is not supported in schema version " +
125+ schemaVersion + ". Minimum required version: " + minVersion );
126+ }
127+ }
128+ } catch (NoSuchFieldException e ) {
129+ throw new IllegalArgumentException ("Unknown algorithm: " + algorithm );
130+ }
131+ }
132+
133+ private static MessageDigest getDigestForAlgorithm (Hash .Algorithm algorithm ) {
134+ try {
135+ switch (algorithm ) {
136+ case MD5 :
137+ return DigestUtils .getMd5Digest ();
138+ case SHA1 :
139+ return DigestUtils .getSha1Digest ();
140+ case SHA_256 :
141+ return DigestUtils .getSha256Digest ();
142+ case SHA_384 :
143+ return DigestUtils .getSha384Digest ();
144+ case SHA_512 :
145+ return DigestUtils .getSha512Digest ();
146+ case SHA3_256 :
147+ return DigestUtils .getSha3_256Digest ();
148+ case SHA3_384 :
149+ return DigestUtils .getSha3_384Digest ();
150+ case SHA3_512 :
151+ return DigestUtils .getSha3_512Digest ();
152+ case BLAKE2b_256 :
153+ case BLAKE2b_384 :
154+ case BLAKE2b_512 :
155+ case BLAKE3 :
156+ return MessageDigest .getInstance (algorithm .getSpec ());
157+ default :
158+ throw new IllegalArgumentException ("Unsupported algorithm: " + algorithm .getSpec ());
159+ }
160+ } catch (NoSuchAlgorithmException | NoSuchMethodError e ) {
161+ throw new IllegalArgumentException ("Algorithm not available: " + algorithm .getSpec (), e );
162+ }
163+ }
164+
90165 private static Hash .Algorithm toAlgorithm (MessageDigest digest ) {
91166 for (Hash .Algorithm value : Hash .Algorithm .values ()) {
92167 if (value .getSpec ().equals (digest .getAlgorithm ())) {
0 commit comments