Skip to content

Commit

Permalink
Merge pull request #5 from Contrast-Security-OSS/shaded-dependencies
Browse files Browse the repository at this point in the history
Added the ability to resolve shaded dependencies.
  • Loading branch information
planetlevel authored Feb 11, 2022
2 parents 4a39ee1 + baf2ab5 commit 1fb1759
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 40 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@
<artifactId>picocli</artifactId>
<version>4.6.2</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
<scope>test</scope>
</dependency>


<!-- TEST -->
<dependency>
Expand Down
19 changes: 11 additions & 8 deletions src/main/java/com/contrastsecurity/Jbom.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,24 +164,27 @@ public void doLocalProcess(String pid, String exclude, String outputDir, String
attach( 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 );
Expand All @@ -191,7 +194,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();
Expand Down
65 changes: 42 additions & 23 deletions src/main/java/com/contrastsecurity/Libraries.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -32,6 +33,8 @@ public class Libraries {
private Set<String> codesourceExamined = new HashSet<>();
private Set<Component> libraries = new HashSet<>();
private Set<org.cyclonedx.model.Dependency> dependencies = new HashSet<>();
private Hash rootSHA1;
private Hash rootMD5;

public void runScan(File jarPath) throws Exception {
addAllLibraries( null, jarPath.getAbsolutePath() );
Expand Down Expand Up @@ -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" );
Expand All @@ -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) {
Expand All @@ -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." );
}
}
}
}
Expand Down Expand Up @@ -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 ) {
Expand All @@ -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( "!/" ) ) {
Expand Down
15 changes: 10 additions & 5 deletions src/main/java/com/contrastsecurity/Library.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
72 changes: 68 additions & 4 deletions src/test/java/com/contrastsecurity/LibrariesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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> 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));
}









}
Binary file added src/test/resources/callback-2.18.0-SNAPSHOT.jar
Binary file not shown.
Binary file not shown.

0 comments on commit 1fb1759

Please sign in to comment.