Last week, I wrote about the JVM policy file that explicitly lists allowed sensitive API calls when running the JVM in sandboxed mode. This week, I’d like to improve the security by signing the JAR.
This is the 3rd post in the JVM Security focus series.Other posts include:
- The Java Security Manager: why and how?
- Proposal for a Java policy files crafting process
- Signing and verifying a standalone JAR (this post)
- Crafting Java policy files, a practical guide
- Beware the Attach API
The nominal way
Create a keystore
The initial step is to create a keystore if none is already available. There are plenty of online tutorials showing how to do that.
keytool -genkey -keyalg RSA -alias selfsigned -keystore /path/to/keystore.jks -storepass password -validity 360
Fill in information accordingly.
Sign the application JAR
Signing the application JAR must be part of the build process. With Maven, the JAR signer plugin is dedicated to that. Its usage is quite straightforward:
<plugin>
<artifactId>maven-jarsigner-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>sign</id>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
<configuration>
<keystore>/path/to/keystore.jks</keystore>
<alias>selfsigned</alias>
<storepass>${store.password}</storepass>
<keypass>${key.password}</keypass>
</configuration>
</plugin>
To create the JAR, invoke the usual command-line and pass both passwords as system properties:
mvn package -Dstore.password=password -Dkey.password=password
Alternatively, Maven’s encryption capabilities can be used to store passwords in a dedicated settings-security.xml
to further improve security.
Configure the policy file
Once the JAR is signed, the policy file can be updated to make use of it. This requires only the following configuration steps:
- Point to the keystore
- Configure the allowed alias
keystore "keystore.jks";
grant signedBy "selfsigned" codeBase "file:target/spring-petclinic-1.4.2.jar" {
...
}
Notice the signedBy
keyword followed by the alias name - the same one as in the keystore above.
Launching the JAR with the policy file
The same launch command can be used without any change:
java -Djava.security.manager -Djava.security.policy=jvm.policy -jar target/spring-petclinic-1.4.2.jar
Unfortunately, it doesn’t work though this particular permission had already been configured!
Caused by: java.security.AccessControlException: access denied ("java.lang.reflect.ReflectPermission" "suppressAccessChecks") at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472) at java.security.AccessController.checkPermission(AccessController.java:884) at java.lang.SecurityManager.checkPermission(SecurityManager.java:549) at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:128) at org.springframework.util.ReflectionUtils.makeAccessible(ReflectionUtils.java:475) at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:141) at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:420)
The strangest part is that permissions requested before this one work all right. The reason is to be found in the particular structure of the JAR created by the Spring Boot plugin: JAR dependencies are packaged untouched in a BOOT-INF/lib
folder in the executable JAR. Then Spring Boot code uses custom class-loading magic to load required classes from there.
JAR signing works by creating a specific hash for each class, and by writing them into the JAR manifest file. During the verification phase, the hash of a class is computed and compared to the hash of the manifest. Hence, permissions related to classes located in the BOOT-INF/classes
folder work as expected.
However, the org.springframework.boot.SpringApplication
class mentioned in the stack trace above is part of the spring-boot.jar
located under BOOT-INF/lib
: verification fails as there’s no hash available for the class in the manifest.
Thus, usage of the Spring Boot plugin for JAR creation/launch is not compatible with JAR signing.
The workaround
Aside from Spring Boot, there’s a legacy way to create standalone JARs: the Maven Shade plugin. This will extract every class of every dependency in the final JAR. This is possible with Spring Boot apps, but it requires some slight changes to the POM:
- In the POM, remove the Spring Boot Maven plugin
- Configure the main class in the Maven JAR plugin:
<plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> <configuration> <archive> <manifest> <mainClass>org.springframework.samples.petclinic.PetClinicApplication</mainClass> </manifest> </archive> </configuration> </plugin>
- Finally, add the Maven Shade plugin to work its magic:
<plugin> <artifactId>maven-shade-plugin</artifactId> <version>2.4.3</version> <configuration> <minimizeJar>true</minimizeJar> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin>
The command-line to launch the JAR doesn’t change but permissions depend on the executed code, coupled to the JAR structure. Hence, the policy file should be slightly modified. |
Lessons learned
While it requires to be a little creative, it’s entirely possible to sign Spring Boot JARs by using the same techniques as for any other JARs.
To go further: