Protecting Web Application ARchives (WAR files), Enterprise ARchives (EAR files) and multiple JARs

Here is a simple Ant task (+JavaScript) that is suitable for WAR files protection (Download). You might use the similar approach when you need to protect multiple JAR files. This way gives you ease and flexibility in configuration.

<project name="Stringer WAR Protection task" default="protect-war" basedir=".">
    <description>
        Stringer WAR file protection
    </description>
  <!-- set global properties for this build -->

    <!-- Stringer-related properties and setups -->
    <!-- Path to the source (unprotected) war file -->
    <property name="stringer.war.name"  location="${basedir}/Test.war"/>
    <!-- Path to Stringer -->
    <property name="stringer.home"  location="/Users/developer/Developments/stringer-ent-2.5.19"/> 
    <!-- <property name="stringer.home"  location="../.."/> -->
    <!-- Creating a temporary directory -->
    <tempfile property="stringer.unpacking.dir" destDir="${java.io.tmpdir}" prefix="stringer"/>
    <!-- Defining stringer task -->
    <taskdef name="stringer" classname="com.licel.stringer.AntTask" classpath="${stringer.home}/stringer.jar"/>
    <!-- End of Stringer-related properties and setups -->

    <!-- Protect the war using Stringer Java Obfuscator -->
    <target name="protect-war">
        <script language="javascript"><![CDATA[
var version = java.lang.System.getProperty("java.version");
if (version.startsWith("1.8.0")) {
    load("nashorn:mozilla_compat.js");
}

importClass(java.io.File);

// backing up the original war file and unpacking the war file to a temporary directory

var pathToWar = project.getProperty("stringer.war.name");
var s = new java.io.File(pathToWar);
var t = new java.io.File(pathToWar+".original");
var copyTask = project.createTask("copy");
copyTask.setFile(s);
copyTask.setTofile(t);
copyTask.execute();
var unzipTask = project.createTask("unzip");
var d = new java.io.File(project.getProperty("stringer.unpacking.dir"));
unzipTask.setSrc(s);
unzipTask.setDest(d);
unzipTask.execute();
print("JS: Path to WAR: " + s.getCanonicalPath());

// protecting classes
print("JS: Protecting classes");
var sourceClasses = project.getProperty("stringer.unpacking.dir")+"/WEB-INF/classes";
var stringerTask = project.createTask("stringer");
stringerTask.setSrcDir(new java.io.File(sourceClasses));
stringerTask.setDestDir(new java.io.File(project.getProperty("stringer.unpacking.dir")+"/WEB-INF/protected-classes"));
customConfigFile = new java.io.File(project.getProperty("basedir") + "/stringer-configs-enabled/classes.xml");
if (customConfigFile.exists()) {
    print("JS: A custom configuration file has been found at " + customConfigFile.toString() + ". Default settings (from the JS file) are ignored.");
    stringerTask.setConfigFile(customConfigFile);
} else {
    print("JS: Unable to find a custom configuration file for classes" + " at " + customConfigFile.toString() + ", proceeding with the default settings (from the JS file).");
    stringerTask.setCheckCaller(true);
    stringerTask.setIntegrityProtection(true);
    stringerTask.setVerbose(true);
    stringerTask.setCheckJar(false);
    //stringerTask.createInclude().setName("com/licel/**");
    //stringerTask.createInclude().setName("com/oracle/**");
    //stringerTask.createExclude().setName("org/apache/**");
    //stringerTask.createExclude().setName("org/test/**");
}
stringerTask.execute();

// protecting jars
print("JS: Protecting JARs");
var fs = project.createDataType("fileset");
fs.setDir(new java.io.File(project.getProperty("stringer.unpacking.dir")+"/WEB-INF/lib"));
fs.setIncludes( "*.jar" );
//fs.setIncludes( "core*.jar,mail*.jar" );
//fs.setExcludes( "*.jar" );
srcFiles = fs.getDirectoryScanner(project).getIncludedFiles();
print("JS: Quantity of JARs to protect: " + srcFiles.length);
for (i = 0; i < srcFiles.length; i++) {
    var filename = srcFiles[i];
    print("JS: Working with " + filename);
    var stringerTask = project.createTask("stringer");
    stringerTask.setSrcFile(new java.io.File(project.getProperty("stringer.unpacking.dir")+"/WEB-INF/lib/"+filename));
    stringerTask.setDestFile(new java.io.File(project.getProperty("stringer.unpacking.dir")+"/WEB-INF/lib/"+filename)); 
    customConfigFile = new java.io.File(project.getProperty("basedir") + "/stringer-configs-enabled/" + filename.replace(".jar",".xml"));
    if (customConfigFile.exists()) {
        print("JS: A custom configuration file has been found at " + customConfigFile.toString() + ". Default settings (from the JS file) are ignored.");
        stringerTask.setConfigFile(customConfigFile);
    } else {
        print("JS: Unable to find a custom configuration file for " + filename + " at " + customConfigFile.toString() + ", proceeding with the default settings.");
        stringerTask.setCheckCaller(true);
        stringerTask.setIntegrityProtection(true);
        stringerTask.setOptimize(false);
        stringerTask.setVerbose(true);
        stringerTask.setCheckJar(false);
        //stringerTask.createInclude().setName("com/licel/**");
        //stringerTask.createInclude().setName("com/oracle/**");
        //stringerTask.createExclude().setName("org/apache/**");
        //stringerTask.createExclude().setName("org/test/**"); 
    }
    stringerTask.execute(); 
}

// removing unnecessary directories and moving right ones to their right places 
var deleteTask = project.createTask("delete");
deleteTask.setDir(new java.io.File(project.getProperty("stringer.unpacking.dir")+"/WEB-INF/classes"));
deleteTask.execute();


var moveTask = project.createTask("move");
moveTask.setFile(new java.io.File(project.getProperty("stringer.unpacking.dir")+"/WEB-INF/protected-classes"));
moveTask.setTofile(new java.io.File(project.getProperty("stringer.unpacking.dir")+"/WEB-INF/classes"));
moveTask.execute();


// packing war back to zip
var zipTask = project.createTask("zip");
// **************************************************
zipTask.setDestFile(new java.io.File(project.getProperty("stringer.war.name")));
var fileSet = project.createDataType("fileset");
fileSet.setDir(new java.io.File(project.getProperty("stringer.unpacking.dir")));
zipTask.addFileset(fileSet);
print("JS: Building a protected WAR. Input: "+project.getProperty("stringer.unpacking.dir"));
var allPath = project.getReference("stringer.unpacking.dir");
if(allPath!=null) {
var paths = allPath.list();
for (i=0; i<paths.length; i++) {
print("JS: Input: "+paths[i]);
fileSet = project.createDataType("fileset");
fileSet.setFile(new java.io.File(paths[i]));
//fileSet.setExcludes("META-INF/**");
zipTask.addZipGroupFileset(fileSet);
}
}
zipTask.execute(); 
// removing temporary directory
print(project.getProperty("stringer.unpacking.dir"));
deleteTask.setDir(new java.io.File(project.getProperty("stringer.unpacking.dir")));
deleteTask.execute();
print("JS: The protected WAR file has been placed at "+project.getProperty("stringer.war.name"));
        ]]></script>
    </target>
</project>

Let's assume you saved the code above into the stringer-war-protection.xml, then you might execute the task by launching:

ant -f stringer-war-protection.xml

Necessary modifications:

a. Modify the value of the stringer.home property in the task (Line 11).

b. Modify the value of the stringer.war.name property in the task (Line 09)

c. Change Stringer’s configuration (if it is needed) in the task (two sections, the first one is for classes, the second is for libs), by default String Encryption, Check Caller and Integrity Protection are enabled both for classes and libs. Class filters are available as well, see examples in the code. In the fs.setIncludes( ".jar" ); (Line 72) you could list comma-separated patterns of files that must be included, for example fs.setIncludes( "jcb.jar, *licel.jar, core-2.4.0.jar" ); You could use exclusions (line 74), as well, please refer to http://ant.apache.org/manual/Types/fileset.html.

This script also looks for custom configuration files for your JARs and classes, by default it looks for them in $PROJECT_HOME/stringer-configs-enabled. For classes it will be $PROJECT_HOME/stringer-configs-enabled/classes.xml, and for JARs - $PROJECT_HOME/stringer-configs-enabled/<your_jar_name>.xml (see lines 51+ and 83+).

Important notes: Please make sure you set setCheckJar(false). It may not work with all the applications servers. It is also important to say that checkCaller is a quite expensive function. It certainly adds more security, but we would not recommend to use it for classes that contain insensitive strings. Please do not protect open source libraries, it will probably lead to a negative performance impact and surely will not add any reverse-engineering resistance.

Here is a reference task for EARs (Download):

```
<project name="Stringer EAR Protection task" default="protect-ear" basedir=".">
    <description>
        Stringer EAR file protection
    </description>
  <!-- set global properties for this build -->

    <!-- Stringer-related properties and setups -->
    <!-- Path to the source (unprotected) ear file -->
    <property name="stringer.ear.name"  location="${basedir}/test.ear"/>
    <!-- Path to Stringer -->
    <property name="stringer.home"  location="/Users/receiver/stringer-ent-2.5.19"/> 
    <!-- <property name="stringer.home"  location="../.."/> -->
    <!-- Creating a temporary directory -->
    <tempfile property="stringer.unpacking.dir" destDir="${java.io.tmpdir}" prefix="stringer"/>
    <!-- Defining stringer task -->
    <taskdef name="stringer" classname="com.licel.stringer.AntTask" classpath="${stringer.home}/stringer.jar"/>
    <!-- End of Stringer-related properties and setups -->

    <!-- Protect the war using Stringer Java Obfuscator -->
    <target name="protect-ear">
        <script language="javascript"><![CDATA[
var version = java.lang.System.getProperty("java.version");
if (version.startsWith("1.8.0")) {
    load("nashorn:mozilla_compat.js");
}

importClass(java.io.File);

function unzipEar(pathToEar, pathToUnpackingDir) {
    var s = new java.io.File(pathToEar);
    var t = new java.io.File(pathToEar+".original");
    var copyTask = project.createTask("copy");
    copyTask.setFile(s);
    copyTask.setTofile(t);
    // backing up the original ear file 
    copyTask.execute(); 
    print("JS: The original EAR file has been backed up into" + t.getCanonicalPath());
    var d = new java.io.File(pathToUnpackingDir);
    var unzipTask = project.createTask("unzip");
    unzipTask.setSrc(s);
    unzipTask.setDest(d);
    unzipTask.execute();
    print("JS: The EAR has been extracted to: " + d.getCanonicalPath());
}

function protectJars(pathToJars, warName) {
    var fs = project.createDataType("fileset");
    fs.setDir(pathToJars);
    fs.setIncludes( "*.jar" );
    //fs.setIncludes( "core*.jar,mail*.jar" );
    //fs.setExcludes( "*.jar" );
    jars = fs.getDirectoryScanner(project).getIncludedFiles();
    for (i=0; i<jars.length; i++) {
        print("JS: Working with " + jars[i]);
        var stringerTask = project.createTask("stringer");
        stringerTask.setSrcFile(new java.io.File(pathToJars+"/"+jars[i]));
        stringerTask.setDestFile(new java.io.File(pathToJars+"/"+jars[i])); 
        customConfigFile = new java.io.File(project.getProperty("basedir") + "/stringer-configs-enabled/" + (warName ? warName.replace(".war","/") : "")+ jars[i].replace(".jar",".xml"));
        if (customConfigFile.exists()) {
            print("JS: A custom configuration file has been found at " + customConfigFile.toString() + ". Default settings (from the JS file) are ignored.");
            stringerTask.setConfigFile(customConfigFile);
        } else {
            print("JS: Unable to find a custom configuration file for " + jars[i] + " at " + customConfigFile.toString() + ", proceeding with the default settings (from the JS file).");
            stringerTask.setCheckCaller(true);
            stringerTask.setIntegrityProtection(true);
            stringerTask.setOptimize(false);
            stringerTask.setCheckJar(false);
            stringerTask.setVerbose(false);
            //stringerTask.createInclude().setName("com/licel/**");
            //stringerTask.createInclude().setName("com/oracle/**");
            //stringerTask.createExclude().setName("org/apache/**");
            //stringerTask.createExclude().setName("org/test/**"); 
        }
        stringerTask.execute(); 
    }
}

function protectWars() {
    for (i=0; i<wars.length; i++) {
        var sourceWar = new java.io.File(pathToUnpackingDir+"/"+wars[i]);
        var extractedWar = new java.io.File(pathToUnpackingDir+"/"+wars[i]+".tmp");
        var unzipTask = project.createTask("unzip");
        unzipTask.setSrc(sourceWar);
        unzipTask.setDest(extractedWar);
        unzipTask.execute();
        var stringerSourceClasses = new java.io.File(extractedWar.toString()+"/WEB-INF/classesToProtect");
        var stringerDestinationClasses = new java.io.File(extractedWar.toString()+"/WEB-INF/classes");
        if (stringerDestinationClasses.exists()) {
            var moveTask = project.createTask("move");
            moveTask.setFile(stringerDestinationClasses);
            moveTask.setTofile(stringerSourceClasses);
            moveTask.execute();
            print("JS: Protecting classes");
            var stringerTaskClasses = project.createTask("stringer");
            stringerTaskClasses.setSrcDir(stringerSourceClasses);
            stringerTaskClasses.setDestDir(stringerDestinationClasses);
            customConfigFile = new java.io.File(project.getProperty("basedir") + "/stringer-configs-enabled/"+wars[i].replace(".war","")+"/classes.xml");
            if (customConfigFile.exists()) {
                print("JS: A custom configuration file has been found at " + customConfigFile.toString() + ". Default settings (from the JS file) are ignored.");
                stringerTaskClasses.setConfigFile(customConfigFile);
            } else {
                print("JS: Unable to find a custom configuration file for classes" + " at " + customConfigFile.toString() + ", proceeding with the default settings (from the JS file).");
                stringerTaskClasses.setCheckCaller(true);
                stringerTaskClasses.setIntegrityProtection(true);
                stringerTaskClasses.setVerbose(false);
                //stringerTaskClasses.createInclude().setName("com/licel/**");
                //stringerTaskClasses.createInclude().setName("com/oracle/**");
                //stringerTaskClasses.createExclude().setName("org/apache/**");
                //stringerTaskClasses.createExclude().setName("org/test/**");
            }
            stringerTaskClasses.execute();
            var deleteTask = project.createTask("delete");
            deleteTask.setDir(stringerSourceClasses);
            deleteTask.execute();
        }
        pathToJars=new java.io.File(extractedWar.toString()+"/WEB-INF/lib");
        fs.setDir(pathToJars);
        fs.setIncludes( "*.jar" );
        //fs.setIncludes( "core*.jar,mail*.jar" );
        //fs.setExcludes( "*.jar" );
        srcFiles = fs.getDirectoryScanner(project).getIncludedFiles();
        print("JS: Quantity of JARs to protect: " + srcFiles.length);

        protectJars(pathToJars, wars[i]);
        var deleteTask = project.createTask("delete");
        deleteTask.setFile(sourceWar);
        deleteTask.execute();

        // **************************************************
        var zipTask = project.createTask("zip");
        zipTask.setDestFile(sourceWar);
        var fileSet = project.createDataType("fileset");
        fileSet.setDir(extractedWar);
        //fileSet.setExcludes("META-INF/**");
        zipTask.addFileset(fileSet);
        print("JS: Building a protected WAR. Input: "+extractedWar.toString()+". Output:"+sourceWar.toString());
        zipTask.execute(); 
        var deleteTask = project.createTask("delete");
        deleteTask.setDir(extractedWar);
        deleteTask.execute();
    }
}

function zipEar(pathToEar, pathToUnpackingDir){
    // packing the protected ear back to zip
    // **************************************************
    var zipTask = project.createTask("zip");
    zipTask.setDestFile(new java.io.File(pathToEar));
    var fileSet = project.createDataType("fileset");
    fileSet.setDir(new java.io.File(pathToUnpackingDir));
    //fileSet.setExcludes("META-INF/**");
    zipTask.addFileset(fileSet);
    print("JS: Building a protected EAR. Input: "+pathToUnpackingDir+". Output: " + pathToEar);
    zipTask.execute(); 
}

function deleteTemporaryDirsAndFiles() {
    var deleteTask = project.createTask("delete");
    deleteTask.setDir(pathToJunk);
    deleteTask.execute();
}

var pathToEar = project.getProperty("stringer.ear.name");
var pathToUnpackingDir = project.getProperty("stringer.unpacking.dir");
var fs = project.createDataType("fileset");

//
unzipEar(pathToEar, pathToUnpackingDir);

var pathToJars = new java.io.File(project.getProperty("stringer.unpacking.dir"));
//
protectJars(pathToJars,null);

fs.setDir(new java.io.File(pathToUnpackingDir));
fs.setIncludes( "*.war" );
//fs.setIncludes( "core*.war,mail*.war" );
//fs.setExcludes( "*.war" );
var wars = fs.getDirectoryScanner(project).getIncludedFiles();
//
protectWars();

zipEar(pathToEar, pathToUnpackingDir);

var pathToJunk = new java.io.File(pathToUnpackingDir);
deleteTemporaryDirsAndFiles();
        ]]></script>
    </target>
</project>

```

These scripts are for your reference, you might modify them in any way you want. If you have any questions of how to adapt the task for your certain case, please do not hesitate to email us.