Description
Problem:
The AWS Encryption SDK fails when performing AwsCrypto#encryptData(KmsMasterKeyProvider, byte[], Map<String, String>)
with an ExceptionInInitializerError
when another JAR containing a project.properties
file appears earlier in the classpath. This causes VersionInfo.versionNumber()
to return null
, leading to a NullPointerException
in ApiName
initialization.
Root Cause
The VersionInfo.versionNumber()
method uses getResourceAsStream("project.properties")
, making it vulnerable to classpath ordering issues when other JARs on the classpath contain the same resource. When another JAR containing project.properties
and lacks a version
property, and appears earlier in the classpath, it gets loaded instead of the SDK's own project.properties
.
If another project.properties
appears earlier in the class and does contain a version
property, I assume the library would continue to load, but may run into extremely subtle problems if the version
is not the expected value.
Environment
AWS Encryption SDK 2.4.0
Java 17
Spring Boot 2.6.x
Gradle
Stack trace
Exception in thread "grpc-default-executor-0" java.lang.ExceptionInInitializerError
at com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKeyProvider.getMasterKey(KmsMasterKeyProvider.java:335)
at com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKeyProvider.getMasterKey(KmsMasterKeyProvider.java:34)
at com.amazonaws.encryptionsdk.MasterKeyProvider.getMasterKey(MasterKeyProvider.java:51)
at com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKeyProvider.getMasterKeysForEncryption(KmsMasterKeyProvider.java:348)
at com.amazonaws.encryptionsdk.DefaultCryptoMaterialsManager.getMaterialsForEncrypt(DefaultCryptoMaterialsManager.java:88)
at com.amazonaws.encryptionsdk.AwsCrypto.encryptData(AwsCrypto.java:355)
at com.amazonaws.encryptionsdk.AwsCrypto.encryptData(AwsCrypto.java:335)
...
Caused by: java.lang.NullPointerException: version must not be null
at software.amazon.awssdk.utils.Validate.notNull(Validate.java:119)
at software.amazon.awssdk.core.ApiName.<init>(ApiName.java:34)
at software.amazon.awssdk.core.ApiName.<init>(ApiName.java:28)
at software.amazon.awssdk.core.ApiName$BuilderImpl.build(ApiName.java:97)
at com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKey.<clinit>(KmsMasterKey.java:47)
...
The relevant static initializer from the KmsMasterKey
class is for the API_NAME
field:
private static final ApiName API_NAME =
ApiName.builder().name(VersionInfo.apiName()).version(VersionInfo.versionNumber()).build();
The relevant code from the VersionInfo
class is:
/*
* String representation of the library version e.g. 2.3.3
*/
public static String versionNumber() {
try {
final Properties properties = new Properties();
final ClassLoader loader = VersionInfo.class.getClassLoader();
properties.load(loader.getResourceAsStream("project.properties"));
return properties.getProperty("version");
} catch (final IOException ex) {
return UNKNOWN_VERSION;
}
}
Reproduction
Include the encryption-sdk and another jar containing project.properties
on your classpath. Ensure the other jar is first in the classpath.
We happen to use https://github.com/googleads/google-ads-java/releases/tag/33.0.0 which (luckily) contains an empty project.properties
file.
When the JVM loads the KmsMasterKey
class, the class loader will find the incorrect project.properties
file.
Solution:
- use a different class loader which prefers looking for the encryption-sdk first
- somehow iterate over all resources named
project.properties
and ensure you've picked the correct one - use a more specific name for
project.properties
Out of scope:
Is there anything the solution will intentionally NOT address?