diff --git a/pom.xml b/pom.xml index 699024f..1dd155f 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,13 @@ picocli 4.6.2 + + commons-codec + commons-codec + 1.15 + test + + diff --git a/src/main/java/com/contrastsecurity/Jbom.java b/src/main/java/com/contrastsecurity/Jbom.java index a39eeb4..eac96c9 100644 --- a/src/main/java/com/contrastsecurity/Jbom.java +++ b/src/main/java/com/contrastsecurity/Jbom.java @@ -135,24 +135,27 @@ public void doLocalProcess(String pid, String exclude, String outputDir, String generateBOM( pid, name); } } - public Libraries doLocalFile(String file, String outputDir) { + File f = new File( file ); + return doLocalFile(f,outputDir); + } + + + public Libraries doLocalFile(File file, String outputDir) { Logger.log( "Analyzing file " + file ); Libraries libs = new Libraries(); - - File f = new File( file ); - if ( !f.exists() ) { + if ( !file.exists() ) { Logger.log( "Could not find file: " + file ); } - if ( !f.isFile() ) { + if ( !file.isFile() ) { Logger.log( "Could not open file: " + file ); } - if ( !libs.isArchive( file ) ) { + if ( !libs.isArchive( file.getAbsolutePath() ) ) { Logger.log( "File does not appear to be an archive: " + file ); } try{ - String name = file; + String name = file.getName(); int idx = name.lastIndexOf('/'); if ( idx != -1 ) { name = name.substring( idx + 1 ); @@ -162,7 +165,7 @@ public Libraries doLocalFile(String file, String outputDir) { name = name.substring( 0, idx ); } name = outputDir + "/jbom-" + name + ( tag == null ? "" : "-" +tag ) + ".json"; - libs.runScan( f ); + libs.runScan( file ); libs.save(name); }catch(Exception e){ e.printStackTrace(); diff --git a/src/main/java/com/contrastsecurity/Libraries.java b/src/main/java/com/contrastsecurity/Libraries.java index 348fd42..1d350cd 100644 --- a/src/main/java/com/contrastsecurity/Libraries.java +++ b/src/main/java/com/contrastsecurity/Libraries.java @@ -16,6 +16,7 @@ import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.jar.Manifest; +import java.util.stream.Collectors; import com.github.packageurl.PackageURL; @@ -32,6 +33,8 @@ public class Libraries { private Set codesourceExamined = new HashSet<>(); private Set libraries = new HashSet<>(); private Set dependencies = new HashSet<>(); + private Hash rootSHA1; + private Hash rootMD5; public void runScan(File jarPath) throws Exception { addAllLibraries( null, jarPath.getAbsolutePath() ); @@ -72,35 +75,18 @@ public void addAllLibraries( Class clazz, String codesource ) { codesourceExamined.add( path ); File f = new File( path ); - Library lib = new Library( parts[parts.length-1] ); // last segment - lib.parsePath( path ); - lib.setType( Library.Type.LIBRARY ); - lib.addProperty( "codesource", path ); - Logger.debug( "MAIN: " + codesource ); - - // add Contrast custom properties - lib.addProperty("source", "Contrast Security - https://contrastsecurity.com"); - lib.addProperty("tool", "jbom - https://github.com/Contrast-Security-OSS/jbom"); - lib.setScope( Scope.REQUIRED ); - - libraries.add( lib ); - invoked.add( lib ); - - JarInputStream jis1 = new JarInputStream( new FileInputStream( f ) ); - String sha1 = hash( jis1, MessageDigest.getInstance("SHA1") ); - lib.addHash( new Hash( Hash.Algorithm.SHA1, sha1 ) ); + String sha1 = hash( new FileInputStream( f ), MessageDigest.getInstance("SHA1") ); + rootSHA1 = new Hash( Hash.Algorithm.SHA1, sha1 ); - JarInputStream jis2 = new JarInputStream( new FileInputStream( f ) ); - String md5 = hash( jis2, MessageDigest.getInstance("MD5") ); - lib.addHash( new Hash( Hash.Algorithm.MD5, md5 ) ); - - lib.addProperty( "maven", "https://search.maven.org/search?q=1:" + sha1 ); + String md5 = hash( new FileInputStream( f ), MessageDigest.getInstance("MD5") ); + rootMD5 = new Hash( Hash.Algorithm.MD5, md5 ); // scan for nested libraries JarInputStream jis3 = new JarInputStream( new FileInputStream( f ) ); JarFile jarfile = new JarFile( f ); scan( jarfile, jis3, f.getAbsolutePath() ); + addRootHashesToRootJar(); } catch( Exception e ) { Logger.log( "The jbom project needs your help to deal with unusual CodeSources." ); Logger.log( "Report issue here: https://github.com/Contrast-Security-OSS/jbom/issues/new/choose" ); @@ -110,6 +96,15 @@ public void addAllLibraries( Class clazz, String codesource ) { } } + private void addRootHashesToRootJar() { + for( Component lib : libraries.stream() + .filter(lib-> lib.getHashes()==null||lib.getHashes().isEmpty()) + .collect(Collectors.toList())) { + lib.addHash(rootSHA1); + lib.addHash(rootMD5); + } + } + public void scan( JarFile jarFile, JarInputStream jis, String codesource ) throws Exception { JarEntry entry = null; while ((entry = jis.getNextJarEntry()) != null) { @@ -120,6 +115,26 @@ public void scan( JarFile jarFile, JarInputStream jis, String codesource ) throw Logger.log( "Problem extracting metadata from " + entry.getName() + " based on " + codesource + ". Continuing." ); e.printStackTrace(); } + } else if ( isPom(entry)) { + try { + Library innerlib = new Library(); + // FIXME: set Scope.EXCLUDED for non-invoked libraries + innerlib.setScope( Scope.REQUIRED ); + innerlib.parsePath( entry.getName() ); + innerlib.addProperty( "codesource", jarFile.getName() + "!/" + entry.getName() ); + libraries.add( innerlib ); + innerlib.setType( Library.Type.LIBRARY ); + parsePom( jis, innerlib ); + try { + if ( innerlib.getGroup() != null && innerlib.getName() != null ) { + innerlib.setPurl(new PackageURL( PackageURL.StandardTypes.MAVEN, innerlib.getGroup(), innerlib.getName(), innerlib.getVersion(), null, null)); + } + } catch( Exception e ) { + // continue + } + } catch( Exception e ) { + // Logger.log( "Problem parsing POM from " + nestedName + " based on " + codesource + ". Continuing." ); + } } } } @@ -162,7 +177,7 @@ public void scanInner( String codesource, JarFile jarFile, JarInputStream jis, J InputStream nis4 = jarFile.getInputStream( entry ); JarInputStream innerJis4 = new JarInputStream( nis4 ); while ((entry = innerJis4.getNextJarEntry()) != null) { - if ( entry.getName().endsWith( "/pom.xml" ) ) { + if ( isPom(entry) ) { try { parsePom( innerJis4, innerlib ); } catch( Exception e ) { @@ -181,6 +196,10 @@ public void scanInner( String codesource, JarFile jarFile, JarInputStream jis, J } + private boolean isPom(JarEntry entry){ + return !entry.isDirectory()&&entry.getName().endsWith("/pom.xml"); + } + public boolean isArchive( String filename ) { if ( filename.endsWith( "!/" ) ) { diff --git a/src/main/java/com/contrastsecurity/Library.java b/src/main/java/com/contrastsecurity/Library.java index 581758b..1680164 100644 --- a/src/main/java/com/contrastsecurity/Library.java +++ b/src/main/java/com/contrastsecurity/Library.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import com.fasterxml.jackson.annotation.JsonIgnore; import com.github.packageurl.PackageURL; @@ -79,14 +80,18 @@ public String toString() { } @Override - public final boolean equals(Object o) { - Library that = (Library)o; - return this.jar.equals(that.jar); + public boolean equals(Object o) { + if(o instanceof Library) { + Library that = (Library) o; + return (this.getName() + this.getVersion() + this.getGroup()).equals(that.getName() + that.getVersion() + that.getGroup()); + } else { + return false; + } } @Override - public final int hashCode() { - return jar.hashCode(); + public int hashCode() { + return Objects.hash(this.getName()+this.getVersion()+this.getGroup()); } @Override diff --git a/src/test/java/com/contrastsecurity/LibrariesTest.java b/src/test/java/com/contrastsecurity/LibrariesTest.java index f2b0441..4f705c1 100644 --- a/src/test/java/com/contrastsecurity/LibrariesTest.java +++ b/src/test/java/com/contrastsecurity/LibrariesTest.java @@ -3,26 +3,90 @@ import static org.junit.Assert.*; +import org.apache.commons.codec.digest.DigestUtils; +import org.cyclonedx.model.Component; +import org.cyclonedx.model.Hash; import org.junit.Test; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Optional; + public class LibrariesTest { @Test public void testFile() throws Exception { - String jar = "src/test/resources/spring-petclinic-1.5.1.jar"; + File jar = getPathToResource("/spring-petclinic-1.5.1.jar"); Jbom jbom = new Jbom(); Libraries libs = jbom.doLocalFile( jar, "target/test" ); assertTrue( "Incorrect number of libraries found. " + libs.getLibraries().size() + " instead of 135", libs.getLibraries().size() == 135 ); + compareHashToFile(jar,libs,"petclinic"); + + } + + @Test + public void testFileCallBack() throws Exception { + File jar = getPathToResource("/callback-2.18.0-SNAPSHOT.jar"); + Jbom jbom = new Jbom(); + Libraries libs = jbom.doLocalFile( jar, "target/test" ); + assertTrue( "Incorrect number of libraries found. " + libs.getLibraries().size() + " instead of 102", libs.getLibraries().size() == 102 ); + compareHashToFile(jar,libs,"callback"); + } + @Test + public void testFileWithShading() throws Exception { + File jar = getPathToResource("/provider-search-0.0.1-SNAPSHOT.jar"); + Jbom jbom = new Jbom(); + Libraries libs = jbom.doLocalFile( jar, "target/test" ); + assertTrue( "Incorrect number of libraries found. " + libs.getLibraries().size() + " instead of 26", libs.getLibraries().size() == 26 ); + compareHashToFile(jar,libs,"provider-search"); + } @Test public void testDir() throws Exception { - String dir = "src/test/resources"; + File jar = getPathToResource("/"); Jbom jbom = new Jbom(); - Libraries libs = jbom.doLocalDirectory( dir, "target/test" ); - assertTrue( "Incorrect number of libraries found. " + libs.getLibraries().size() + " instead of 138", libs.getLibraries().size() == 138 ); + Libraries libs = jbom.doLocalDirectory( jar.getAbsolutePath(), "target/test" ); + assertTrue( "Incorrect number of libraries found. " + libs.getLibraries().size() + " instead of 265", libs.getLibraries().size() == 265 ); + + } + + private File getPathToResource(String path) throws URISyntaxException { + return new File(LibrariesTest.class.getResource(path).toURI()); } + private void compareHashToFile(File file, Libraries libs, String libName) throws IOException { + Optional component = libs.getLibraries().stream().filter(lib->lib.getName().contains(libName)).findFirst(); + if(!component.isPresent()) { + fail("Library : " + libName + " cannot be found"); + } else { + String sha1FromLib = component.get().getHashes().stream().filter(h->h.getAlgorithm().equals("SHA-1")).map(Hash::getValue).findFirst().orElse("SHA1 Hash Not Found"); + String md5FromLib = component.get().getHashes().stream().filter(h->h.getAlgorithm().equals("MD5")).map(Hash::getValue).findFirst().orElse("MD5 Hash Not Found"); + + assertEquals(hashFileSHA1(file),sha1FromLib); + assertEquals(hashFileMD5(file),md5FromLib); + } + + } + + private String hashFileSHA1(File path) throws IOException { + return DigestUtils.sha1Hex(new FileInputStream(path)); + } + + private String hashFileMD5(File path) throws IOException { + return DigestUtils.md5Hex(new FileInputStream(path)); + } + + + + + + + + + } \ No newline at end of file diff --git a/src/test/resources/callback-2.18.0-SNAPSHOT.jar b/src/test/resources/callback-2.18.0-SNAPSHOT.jar new file mode 100644 index 0000000..4d02c56 Binary files /dev/null and b/src/test/resources/callback-2.18.0-SNAPSHOT.jar differ diff --git a/src/test/resources/provider-search-0.0.1-SNAPSHOT.jar b/src/test/resources/provider-search-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000..645effc Binary files /dev/null and b/src/test/resources/provider-search-0.0.1-SNAPSHOT.jar differ