Skip to content

[BUG] KeyVault JCA Provider ignores JVM truststore sys props #44771

Open
@rgpower

Description

@rgpower

Describe the bug
The JCA KeyStore SPI for Azure KeyVault ignores custom truststore JVM settings, as specified in https://docs.oracle.com/en/java/javase/17/security/java-secure-socket-extension-jsse-reference-guide.html

For example, the following has no affect at all, no matter what is contained in the jks file below - you could even delete all of the trusted roots from it, and it would not affect anything.

-Djavax.net.ssl.trustStore=/tmp/truststore.jks -Djavax.net.ssl.trustStorePassword=changeme

This means that the JCA KeyStore SPI itself, when it is authenticating to Azure, and it is interacting with the keyvault, is limited to using the default cacerts that ship with the JRE, which is not acceptable in many high compliance environments, where trusted roots need to be heavily restricted.

After looking at the code, I discovered a workaround for this bug, where I configure the JVM properties, javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword, to refer to our custom truststore.

The code in question are these two lines:

and

You could even argue that the JreKeyStoreFactory should be renamed JreTrustStoreFactory since it is obviously going after "trusted roots" due to this line:

However this workaround is not that great, since we use a keystore to hold mTLS client certs to authenticate to other services, and we want our trusted roots to be configurable via expected JVM parameters.

It is further possible that it might be desirable in the future to support both keystore and truststore, if this implementation ever supports service principal (SP) client certificate auth, in addition to the SP client secret auth that it supports right now.

Exception or Stack Trace

INFO: Getting access token using client ID / client secret
Disconnected from the target VM, address: 'localhost:45835', transport: 'socket'
Mar 24, 2025 12:57:58 PM com.azure.security.keyvault.jca.implementation.utils.HttpUtil post
WARNING: Unable to finish the HTTP POST request.
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:383)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:326)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
	at ...
	at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
	at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1510)
	at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1425)
	at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:455)
	at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:426)
	at com.azure.security.keyvault.jca.implementation.shaded.org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436)
	at com.azure.security.keyvault.jca.implementation.shaded.org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384)
...
	at com.azure.security.keyvault.jca.implementation.utils.HttpUtil.post(HttpUtil.java:111)
	at com.azure.security.keyvault.jca.implementation.utils.HttpUtil.post(HttpUtil.java:81)
	at com.azure.security.keyvault.jca.implementation.utils.AccessTokenUtil.getAccessToken(AccessTokenUtil.java:156)
	at com.azure.security.keyvault.jca.implementation.KeyVaultClient.getAccessTokenByHttpRequest(KeyVaultClient.java:202)
...
	at com.azure.security.keyvault.jca.implementation.certificates.KeyVaultCertificates.getAliases(KeyVaultCertificates.java:105)
	at com.azure.security.keyvault.jca.KeyVaultKeyStore.<init>(KeyVaultKeyStore.java:151)
...
	at java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:164)
	at java.base/java.security.Security.getImpl(Security.java:676)
	at java.base/java.security.KeyStore.getInstance(KeyStore.java:868)

...
To Reproduce
Configure a empty truststore.jks and configure it using normal JVM parameters like

-Djavax.net.ssl.trustStore=/tmp/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit

The above will still be able to connect to the key vault no problem, which is unexpected.

If you (wrongly/hack-wise) configure JVM keystore settings, instead of using JVM truststore settings via

-Djavax.net.ssl.keyStore=/tmp/truststore.jks -Djavax.net.ssl.keyStorePassword=changeit

The above code will fail with the stack trace noted above ,which is also unexpected.

Finally, if you add the DigiCert Root CA and Root G2 certs to the /tmp/truststore.jks, and configure

-Djavax.net.ssl.keyStore=/tmp/truststore.jks -Djavax.net.ssl.keyStorePassword=changeit

the above will pass, which is unexpected, until you read the code.

Code Snippet

Here's an example that will cause the issue, if JVM settings are configured as described above, the getInstance() call will attempt to do service principal based authentication, and list all of the keys/cert chains in the keyvault.

import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;

public class KeyStoreTest {

  final private static String PROVIDER_CLASSNAME = "com.azure.security.keyvault.jca.KeyVaultJcaProvider";

  static {
    try {
      Class<?> providerClass = Class.forName(PROVIDER_CLASSNAME);
      final Provider provider = (Provider) providerClass.getDeclaredConstructor().newInstance();
      Security.addProvider(provider);
    } catch (Throwable t) {
      t.printStackTrace();
    }
  }

  public static void main(String[] args) throws Throwable {
    KeyStore.getInstance("AzureKeyVault");
  }
}

Expected behavior
I expect Keyvault JCA KeyStore Provider to respect JVM truststore the following JVM settings

-Djavax.net.ssl.trustStore=/tmp/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit

Screenshots
N/A

Setup (please complete the following information):

  • OS: WSL2/Windows
  • IDE: gradlew CLI
  • Library/Libraries: [com.azure:azure-security-keyvault-jca:2.10.0]
  • Java version: 17

I created build.gradle similar to

plugins {
    id 'java'
    id 'application'
}

java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

dependencies {
  implementation 'com.azure:azure-security-keyvault-jca:2.10.0'
}

compileJava {
    options.compilerArgs << '-Xlint:deprecation'
}

def systemProps = project.properties.findAll { it.key.startsWith('systemProp.') }
def jvmArgs = systemProps.collect { key, value -> "-D${key - 'systemProp.'}=${value}" }


application {
    mainClass = 'KeyStoreTest'
    applicationDefaultJvmArgs = jvmArgs
}

I set up a gradle.properties having

systemProp.azure.keyvault.uri=https://fill-in-your-own.vault.azure.net/
systemProp.azure.keyvault.tenant-id=fill-in-your-own
systemProp.azure.keyvault.client-id=fill-in-your-own
systemProp.azure.keyvault.client-secret=fill-in-your-own

Additional context
Add any other context about the problem here.

Information Checklist
Kindly make sure that you have added all the following information above and checkoff the required fields otherwise we will treat the issuer as an incomplete report

  • [ x] Bug Description Added
  • [ x] Repro Steps Added
  • [ x] Setup information Added

Metadata

Metadata

Assignees

Labels

azure-springAll azure-spring related issuesazure-spring-jcacustomer-reportedIssues that are reported by GitHub users external to the Azure organization.questionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

Type

No type

Projects

Status

Todo

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions