Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
### Bugs Fixed
- Stopped recording sensitive data in `FINER` level logs. Review any logs captured at the `FINER` level or lower in previous library versions and rotate any sensitive data contained there.
- Fixed bug: `jarsigner` reports invalid certificate chain (`PKIX path building failed: unable to find valid certification path to requested target`) when using a non-exportable Azure Key Vault certificate. When the certificate chain returned by Azure Key Vault does not end in a self-signed root, the missing issuer certificates are now resolved at runtime using the CA Issuers URL in the AIA (Authority Information Access) extension of each certificate. Responses are cached by URL so subsequent loads can reuse them without another network request. ([#44267](https://github.com/Azure/azure-sdk-for-java/issues/44267))
- Fixed an issue where `KeyStore.load(KeyVaultLoadStoreParameter)` could combine explicit client settings with certificate cache and path settings captured earlier from system properties. The parameter now carries the complete key store configuration. ([#50163](https://github.com/Azure/azure-sdk-for-java/pull/50163))

### Other Changes
- Added system property `azure.keyvault.jca.disable-aia-download` to disable automatic AIA chain completion. AIA chain completion downloads certificates from URLs embedded in certificate extensions, so this allows locked-down environments to prevent those outbound HTTP(S) requests, mitigating potential SSRF-like attack vectors when loading untrusted certificates. Set to `true` to disable (defaults to `false` for backward compatibility).
- Added system property `azure.keyvault.jca.disable-aia-download` to disable automatic AIA chain completion. AIA chain completion downloads certificates from URLs embedded in certificate extensions, so this allows locked-down environments to prevent those outbound HTTP(S) requests, mitigating potential SSRF-like attack vectors when loading untrusted certificates. The value is captured when each Key Vault client is initialized and retained for lazy certificate-chain loading, so multiple keystores can use different settings without overwriting one another. Set to `true` to disable (defaults to `false`).
- Added `KeyVaultJcaPropertyNames` as the central source for the system property names supported by the Azure Key Vault JCA provider. ([#50163](https://github.com/Azure/azure-sdk-for-java/pull/50163))

## 2.12.0 (2026-07-24)

Expand Down
33 changes: 30 additions & 3 deletions sdk/keyvault/azure-security-keyvault-jca/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,44 @@ The JCA library supports configuring the following options:
* `azure.keyvault.jca.certificates-refresh-interval-in-ms`: The refresh interval time.
* `azure.keyvault.jca.certificate-alias-filter-pattern`: A regex that filters which Key Vault certificate aliases are eligible for lazy loading. Append a suffix to the property name to configure more than one filter, for example `azure.keyvault.jca.certificate-alias-filter-pattern.1` or `azure.keyvault.jca.certificate-alias-filter-pattern.prod`. If no such property is configured, all discovered Key Vault aliases are eligible for lazy loading. See "Filtering Key Vault certificate aliases" below.
* `azure.keyvault.disable-challenge-resource-verification`: Indicates whether to disable verification that the authentication challenge resource matches the Key Vault or Managed HSM domain.
* `azure.keyvault.jca.disable-aia-download`: Set to `true` to disable automatic AIA (Authority Information Access) certificate chain completion. Chain completion is only attempted when the chain returned by Azure Key Vault is incomplete, meaning it holds a single certificate or is missing an intermediate CA. When disabled, the provider will return certificate chains as provided by Azure Key Vault without downloading missing intermediate CA certificates. Use this in locked-down environments or when processing untrusted certificates to prevent outbound HTTP(S) requests to URLs embedded in certificate extensions. Defaults to `false` for backward compatibility.
* `azure.keyvault.jca.disable-aia-download`: Set to `true` to disable automatic AIA (Authority Information Access) certificate chain completion. Chain completion is only attempted when the chain returned by Azure Key Vault is incomplete, meaning it holds a single certificate or is missing an intermediate CA. When disabled, the provider will return certificate chains as provided by Azure Key Vault without downloading missing intermediate CA certificates. Use this in locked-down environments or when processing untrusted certificates to prevent outbound HTTP(S) requests to URLs embedded in certificate extensions. Defaults to `false` for backward compatibility. The value is captured when a Key Vault keystore and its client are initialized.

You can configure these properties using:
The supported system property names are available from `KeyVaultJcaPropertyNames`. You can configure them using:
```java
System.setProperty("azure.keyvault.uri", "<your-azure-keyvault-uri>");
System.setProperty(KeyVaultJcaPropertyNames.KEYVAULT_URI, "<your-azure-keyvault-uri>");
```
or as a JVM argument:
```shell
-Dazure.keyvault.uri=<your-azure-keyvault-uri>
```

#### Programmatic configuration

Use `KeyVaultLoadStoreParameter` when each key store needs an explicit configuration instead of global system
properties:

```java
KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter(
"<your-azure-keyvault-uri>",
"<your-tenant-id>",
"<your-client-id>",
"<your-client-secret>")
.setCertificatesRefreshIntervalInMs(60_000)
.setCertificateAliasFilterPatterns(Collections.singleton("^prod-.*"));
parameter.disableAiaDownload();

Security.addProvider(new KeyVaultJcaProvider());
KeyStore keyStore = KeyStore.getInstance(
KeyVaultKeyStore.KEY_STORE_TYPE,
KeyVaultJcaProvider.PROVIDER_NAME);
keyStore.load(parameter);
```

When `load(parameter)` is used, the values and defaults in that parameter replace the complete configuration captured
from system properties. This prevents separate key stores from overwriting one another's configuration. Use
`KeyVaultLoadStoreParameter.fromSystemProperties()` when a programmatic caller needs the same system-property
snapshot used by the default key store initialization.

#### Filtering Key Vault certificate aliases

Each filter is configured as its own property, so no delimiter is required and a pattern may contain any character. Filters use Java-based regex:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.security.keyvault.jca;

/**
* System property names supported by the Azure Key Vault JCA provider.
*/
public final class KeyVaultJcaPropertyNames {

private KeyVaultJcaPropertyNames() {
}

/**
* The Azure Key Vault endpoint property name.
*/
public static final String KEYVAULT_URI = "azure.keyvault.uri";

/**
* The Microsoft Entra tenant ID property name.
*/
public static final String KEYVAULT_TENANT_ID = "azure.keyvault.tenant-id";

/**
* The client ID property name.
*/
public static final String KEYVAULT_CLIENT_ID = "azure.keyvault.client-id";

/**
* The client secret property name.
*/
public static final String KEYVAULT_CLIENT_SECRET = "azure.keyvault.client-secret";

/**
* The managed identity property name.
*/
public static final String KEYVAULT_MANAGED_IDENTITY = "azure.keyvault.managed-identity";

/**
* The access token property name.
*/
public static final String KEYVAULT_ACCESS_TOKEN = "azure.keyvault.access-token";

/**
* The property name used to disable challenge resource verification.
*/
public static final String KEYVAULT_DISABLE_CHALLENGE_RESOURCE_VERIFICATION
= "azure.keyvault.disable-challenge-resource-verification";

/**
* The well-known certificate path property name.
*/
public static final String CERT_PATH_WELL_KNOWN = "azure.cert-path.well-known";

/**
* The custom certificate path property name.
*/
public static final String CERT_PATH_CUSTOM = "azure.cert-path.custom";

/**
* The certificate refresh interval property name.
*/
public static final String KEYVAULT_JCA_CERTIFICATES_REFRESH_INTERVAL
= "azure.keyvault.jca.certificates-refresh-interval";

/**
* The certificate refresh interval in milliseconds property name.
*/
public static final String KEYVAULT_JCA_CERTIFICATES_REFRESH_INTERVAL_IN_MS
= "azure.keyvault.jca.certificates-refresh-interval-in-ms";

/**
* The property name used to refresh certificates when an untrusted certificate is encountered.
*/
public static final String KEYVAULT_JCA_REFRESH_CERTIFICATES_WHEN_HAVE_UNTRUST_CERTIFICATE
= "azure.keyvault.jca.refresh-certificates-when-have-un-trust-certificate";

/**
* The certificate alias filter pattern property name.
*/
public static final String KEYVAULT_JCA_CERTIFICATE_ALIAS_FILTER_PATTERN
= "azure.keyvault.jca.certificate-alias-filter-pattern";

/**
* The property name used to disable Authority Information Access (AIA) certificate downloads.
*/
public static final String KEYVAULT_JCA_DISABLE_AIA_DOWNLOAD = "azure.keyvault.jca.disable-aia-download";

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.logging.Level.FINE;
import static java.util.logging.Level.WARNING;
Expand All @@ -59,9 +53,6 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
*/
private static final Logger LOGGER = Logger.getLogger(KeyVaultKeyStore.class.getName());

static final String CERTIFICATE_ALIAS_FILTER_PATTERN_PROPERTY
= "azure.keyvault.jca.certificate-alias-filter-pattern";

/**
* Stores the Jre key store certificates.
*/
Expand All @@ -70,12 +61,12 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
/**
* Store well Know certificates loaded from specific path.
*/
private final SpecificPathCertificates wellKnowCertificates;
private SpecificPathCertificates wellKnowCertificates;

/**
* Store custom certificates loaded from specific path.
*/
private final SpecificPathCertificates customCertificates;
private SpecificPathCertificates customCertificates;

/**
* Store certificates loaded from KeyVault.
Expand All @@ -90,111 +81,67 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
/**
* Stores all the certificates.
*/
private final List<AzureCertificates> allCertificates;
private List<AzureCertificates> allCertificates;

/**
* Stores the creation date.
*/
private final Date creationDate;

private final boolean refreshCertificatesWhenHaveUnTrustCertificate;
boolean refreshCertificatesWhenHaveUnTrustCertificate;

/**
* Store the path where the well-known certificate is placed
*/
final String wellKnowPath
= Optional.ofNullable(System.getProperty("azure.cert-path.well-known")).orElse("/etc/certs/well-known/");
String certPathWellKnown;

/**
* Store the path where the custom certificate is placed
*/
final String customPath
= Optional.ofNullable(System.getProperty("azure.cert-path.custom")).orElse("/etc/certs/custom/");
String certPathCustom;

/**
* Constructor.
*
* <p>
* The constructor uses System.getProperty for
* <code>azure.keyvault.uri</code>,
* <code>azure.keyvault.tenantId</code>,
* <code>azure.keyvault.clientId</code>,
* <code>azure.keyvault.clientSecret</code> and
* <code>azure.keyvault.managedIdentity</code> to initialize the
* Key Vault client.
* </p>
* <p>The constructor uses {@link KeyVaultLoadStoreParameter#fromSystemProperties()} to capture the supported
* system properties and initialize all certificate sources from one configuration snapshot.</p>
*/
public KeyVaultKeyStore() {
LOGGER.log(FINE, "Constructing KeyVaultKeyStore.");

creationDate = new Date();
String keyVaultUri = System.getProperty("azure.keyvault.uri");
String tenantId = System.getProperty("azure.keyvault.tenant-id");
String clientId = System.getProperty("azure.keyvault.client-id");
String clientSecret = System.getProperty("azure.keyvault.client-secret");
String managedIdentity = System.getProperty("azure.keyvault.managed-identity");
String accessToken = System.getProperty("azure.keyvault.access-token");
boolean disableChallengeResourceVerification
= Boolean.parseBoolean(System.getProperty("azure.keyvault.disable-challenge-resource-verification"));
long refreshInterval = getRefreshInterval();
refreshCertificatesWhenHaveUnTrustCertificate
= Optional.of("azure.keyvault.jca.refresh-certificates-when-have-un-trust-certificate")
.map(System::getProperty)
.map(Boolean::parseBoolean)
.orElse(false);

KeyVaultLoadStoreParameter parameter = KeyVaultLoadStoreParameter.fromSystemProperties();
jreCertificates = JreCertificates.getInstance();
LOGGER.log(FINE, String.format("Loaded jre certificates: %s.", jreCertificates.getAliases()));

wellKnowCertificates = SpecificPathCertificates.getSpecificPathCertificates(wellKnowPath);
LOGGER.log(FINE, String.format("Loaded well known certificates: %s.", wellKnowCertificates.getAliases()));

customCertificates = SpecificPathCertificates.getSpecificPathCertificates(customPath);
LOGGER.log(FINE, String.format("Loaded custom certificates: %s.", customCertificates.getAliases()));

keyVaultCertificates
= new KeyVaultCertificates(refreshInterval, keyVaultUri, tenantId, clientId, clientSecret, managedIdentity,
accessToken, disableChallengeResourceVerification, getKeyVaultCertificateAliasFilterPatterns());
LOGGER.log(FINE, () -> String.format("Loaded Key Vault certificates: %s.", keyVaultCertificates.getAliases()));
keyVaultCertificates = new KeyVaultCertificates(parameter);
LOGGER.log(FINE, "Configured Key Vault certificate source.");

classpathCertificates = new ClasspathCertificates();
LOGGER.log(FINE, String.format("Loaded classpath certificates: %s.", classpathCertificates.getAliases()));

allCertificates = Arrays.asList(jreCertificates, wellKnowCertificates, customCertificates, keyVaultCertificates,
classpathCertificates);
updateKeyStoreConfiguration(parameter);
}

Long getRefreshInterval() {
return Stream
.of("azure.keyvault.jca.certificates-refresh-interval-in-ms",
"azure.keyvault.jca.certificates-refresh-interval")
.map(System::getProperty)
.filter(Objects::nonNull)
.map(Long::valueOf)
.findFirst()
.orElse(0L);
}
private void updateKeyStoreConfiguration(KeyVaultLoadStoreParameter parameter) {
refreshCertificatesWhenHaveUnTrustCertificate = parameter.isRefreshCertificatesWhenHaveUnTrustCertificate();
certPathWellKnown = parameter.getCertPathWellKnown();
certPathCustom = parameter.getCertPathCustom();

wellKnowCertificates = SpecificPathCertificates.getSpecificPathCertificates(certPathWellKnown);
LOGGER.log(FINE, String.format("Loaded well known certificates: %s.", wellKnowCertificates.getAliases()));

customCertificates = SpecificPathCertificates.getSpecificPathCertificates(certPathCustom);
LOGGER.log(FINE, String.format("Loaded custom certificates: %s.", customCertificates.getAliases()));

Set<String> getKeyVaultCertificateAliasFilterPatterns() {
// Each pattern gets its own property because any delimiter character can be part of a regex.
Properties properties = System.getProperties();
String suffixedPropertyPrefix = CERTIFICATE_ALIAS_FILTER_PATTERN_PROPERTY + ".";

return properties.stringPropertyNames()
.stream()
.filter(name -> name.equals(CERTIFICATE_ALIAS_FILTER_PATTERN_PROPERTY)
|| name.startsWith(suffixedPropertyPrefix))
.map(properties::getProperty)
.filter(Objects::nonNull)
.map(String::trim)
.filter(pattern -> !pattern.isEmpty())
.collect(Collectors.toSet());
allCertificates = Arrays.asList(jreCertificates, wellKnowCertificates, customCertificates, keyVaultCertificates,
classpathCertificates);
}

/**
* get key vault key store by system property
* Gets a Key Vault key store configured from a snapshot of the supported system properties.
*
* @return KeyVault key store
* @return The Key Vault key store.
* @throws CertificateException if any of the certificates in the
* keystore could not be loaded
* @throws NoSuchAlgorithmException when algorithm is unavailable.
Expand All @@ -204,17 +151,8 @@ Set<String> getKeyVaultCertificateAliasFilterPatterns() {
public static KeyStore getKeyVaultKeyStoreBySystemProperty()
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {

KeyVaultLoadStoreParameter keyVaultLoadStoreParameter = KeyVaultLoadStoreParameter.fromSystemProperties();
KeyStore keyStore = KeyStore.getInstance(KeyVaultJcaProvider.PROVIDER_NAME);
KeyVaultLoadStoreParameter keyVaultLoadStoreParameter
= new KeyVaultLoadStoreParameter(System.getProperty("azure.keyvault.uri"),
System.getProperty("azure.keyvault.tenant-id"), System.getProperty("azure.keyvault.client-id"),
System.getProperty("azure.keyvault.client-secret"),
System.getProperty("azure.keyvault.managed-identity"))
.setAccessToken(System.getProperty("azure.keyvault.access-token"));

if (Boolean.parseBoolean(System.getProperty("azure.keyvault.disable-challenge-resource-verification"))) {
keyVaultLoadStoreParameter.disableChallengeResourceVerification();
}

keyStore.load(keyVaultLoadStoreParameter);

Expand Down Expand Up @@ -434,6 +372,7 @@ public boolean engineIsKeyEntry(String alias) {

/**
* Loads the keystore using the given {@code KeyStore.LoadStoreParameter}.
* A {@link KeyVaultLoadStoreParameter} replaces the complete configuration captured by the constructor.
*
* @param param the {@code KeyStore.LoadStoreParameter}
* that specifies how to load the keystore,
Expand All @@ -444,9 +383,8 @@ public void engineLoad(KeyStore.LoadStoreParameter param) {
if (param instanceof KeyVaultLoadStoreParameter) {
KeyVaultLoadStoreParameter parameter = (KeyVaultLoadStoreParameter) param;

keyVaultCertificates.updateKeyVaultClient(parameter.getUri(), parameter.getTenantId(),
parameter.getClientId(), parameter.getClientSecret(), parameter.getManagedIdentity(),
parameter.getAccessToken(), parameter.isChallengeResourceVerificationDisabled());
keyVaultCertificates.updateKeyVaultClient(parameter);
updateKeyStoreConfiguration(parameter);
}

classpathCertificates.loadCertificatesFromClasspath();
Expand Down
Loading
Loading