Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6c8ec90
Add configured certificate filtering and lazy loading in KeyVault JCA
rujche Jul 10, 2026
86621f2
Add PR link to keyvault-jca changelog entry
rujche Jul 10, 2026
f4371a4
Address PR review comments for alias refresh and thread safety
rujche Jul 10, 2026
5507d46
Address additional PR review feedback on thread safety and refresh be…
rujche Jul 10, 2026
cc494aa
Make alias lookup use snapshots for thread-safe iteration
rujche Jul 10, 2026
b3cd9d5
Ensure refreshed aliases list remains mutable
rujche Jul 10, 2026
7875700
Restore key vault precedence over classpath in keystore lookups
rujche Jul 10, 2026
a343059
Refine lazy key vault loads and remove brittle reflection test
rujche Jul 10, 2026
a6fbf69
Fix import order and remove unused import
rujche Jul 10, 2026
9d74fac
Strengthen test isolation and list API assertions
rujche Jul 10, 2026
162a828
Retry certificate load when Key Vault returns null
rujche Jul 10, 2026
d588c4d
Suppress GoodLoggingCheck and fix synchronization consistency
rujche Jul 10, 2026
b4aae28
Retry key and chain loads after transient failures
rujche Jul 10, 2026
c553796
Use lazy warning logs and remove extra suppression
rujche Jul 10, 2026
c73eb95
Reset key vault certificate cache when client changes
rujche Jul 10, 2026
72887a0
Make key vault client update atomic and reuse cache reset
rujche Jul 10, 2026
ce99b3e
Ignore null configured certificate aliases
rujche Jul 10, 2026
7ec6baf
Use allCertificates precedence in KeyVaultKeyStore lookups
rujche Jul 10, 2026
f34921f
Add GoodLoggingCheck suppression for KeyVaultCertificates
rujche Jul 10, 2026
9506d26
Guard null certificate alias lookup and clarify refresh docs
rujche Jul 10, 2026
35885e3
Reduce lock contention in lazy certificate loading
rujche Jul 13, 2026
e67607c
Add certificates-filter-patterns property for alias regex filtering
rujche Jul 13, 2026
8cddbf0
Simplify certificates filter pattern README wording
rujche Jul 13, 2026
f1ce384
Align changelog filter pattern wording with README
rujche Jul 13, 2026
470b184
Rename certificate alias filter property
rujche Jul 13, 2026
524655e
Document default alias filter behavior
rujche Jul 13, 2026
7662126
Reorder default filter behavior sentence in README
rujche Jul 13, 2026
60907c2
Fix stale-client publish races in lazy loaders
rujche Jul 13, 2026
d75b0e2
Regenerate linting suppressions for keyvault-jca
rujche Jul 13, 2026
b3120a8
Address review feedback on alias logging and filter parsing
rujche Jul 13, 2026
91a495c
Address review feedback on filter pattern normalization
rujche Jul 13, 2026
a407a55
Refine alias filter error message and state usage
rujche Jul 13, 2026
31c94e2
Harden certificate lookup null safety and key map exposure
rujche Jul 13, 2026
fb84762
Defensively copy exposed certificate collections
rujche Jul 13, 2026
e450841
Refine KeyVault certificate refresh locking
rujche Jul 13, 2026
7c23284
Avoid duplicate certificate chain cloning
rujche Jul 13, 2026
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
2 changes: 2 additions & 0 deletions sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## 2.12.0-beta.1 (Unreleased)

### Features Added
- Added lazy loading for Key Vault certificate details in the JCA keystore. Certificate details are now loaded by alias when requested, avoiding unnecessary reads for unconfigured certificates. ([#49774](https://github.com/Azure/azure-sdk-for-java/pull/49774))
- Added support for `azure.keyvault.jca.certificate-alias-filter-patterns` to filter Key Vault certificate aliases with include/exclude regex patterns. Include patterns are configured directly and exclude patterns are prefixed with `!`. If the property is not configured, alias filtering is disabled and all discovered Key Vault aliases remain eligible for lazy loading. ([#39487](https://github.com/Azure/azure-sdk-for-java/issues/39487))

### Breaking Changes

Expand Down
1 change: 1 addition & 0 deletions sdk/keyvault/azure-security-keyvault-jca/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ The JCA library supports configuring the following options:
* `azure.keyvault.jca.refresh-certificates-when-have-un-trust-certificate`: Indicates whether to refresh certificates when have untrusted certificate.
* `azure.keyvault.jca.certificates-refresh-interval`: The refresh interval time.
* `azure.keyvault.jca.certificates-refresh-interval-in-ms`: The refresh interval time.
Comment thread
rujche marked this conversation as resolved.
* `azure.keyvault.jca.certificate-alias-filter-patterns`: Comma-separated Key Vault alias regex filters. Use include regexes directly and exclude regexes with a `!` prefix. Regex patterns use full-alias matching (`Pattern.matcher(alias).matches()`). An alias is loaded only if it matches at least one include regex (or if no include regex is configured) and matches no exclude regex. Example: `^prod-.*,!^prod-deprecated$,!.*-old$`. If this property is not configured, all discovered Key Vault aliases are eligible for lazy loading.
* `azure.keyvault.disable-challenge-resource-verification`: Indicates whether to disable verification that the authentication challenge resource matches the Key Vault or Managed HSM domain.

You can configure these properties using:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
<suppress files="com.azure.security.keyvault.jca.implementation.KeyVaultClient.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.certificates.ClasspathCertificates.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.certificates.JreCertificates.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.certificates.KeyVaultCertificates.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.certificates.SpecificPathCertificates.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
Comment thread
rujche marked this conversation as resolved.
Comment thread
rujche marked this conversation as resolved.
<suppress files="com.azure.security.keyvault.jca.implementation.utils.AccessTokenUtil.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.utils.HttpUtil.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.utils.JsonConverterUtil.java" checks="io.clientcore.linting.extensions.checkstyle.checks.GoodLoggingCheck" />
<suppress files="com.azure.security.keyvault.jca.KeyVaultTrustManager.java" checks="io.clientcore.linting.extensions.checkstyle.checks.ThrowFromClientLoggerCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.KeyVaultClient.java" checks="io.clientcore.linting.extensions.checkstyle.checks.ThrowFromClientLoggerCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.certificates.KeyVaultCertificates.java" checks="io.clientcore.linting.extensions.checkstyle.checks.ThrowFromClientLoggerCheck" />
Comment thread
rujche marked this conversation as resolved.
<suppress files="com.azure.security.keyvault.jca.implementation.signature.AbstractKeyVaultKeylessSignature.java" checks="io.clientcore.linting.extensions.checkstyle.checks.ThrowFromClientLoggerCheck" />
Comment thread
rujche marked this conversation as resolved.
<suppress files="com.azure.security.keyvault.jca.implementation.signature.KeyVaultKeylessRsaSsaPssSignature.java" checks="io.clientcore.linting.extensions.checkstyle.checks.ThrowFromClientLoggerCheck" />
<suppress files="com.azure.security.keyvault.jca.implementation.utils.HttpUtil.java" checks="io.clientcore.linting.extensions.checkstyle.checks.UseCaughtExceptionCauseCheck" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
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;
Expand All @@ -56,6 +58,9 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
*/
private static final Logger LOGGER = Logger.getLogger(KeyVaultKeyStore.class.getName());

static final String CERTIFICATE_ALIAS_FILTER_PATTERNS_PROPERTY
= "azure.keyvault.jca.certificate-alias-filter-patterns";

/**
* Stores the Jre key store certificates.
*/
Expand Down Expand Up @@ -146,9 +151,10 @@ public KeyVaultKeyStore() {
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);
LOGGER.log(FINE, String.format("Loaded Key Vault certificates: %s.", keyVaultCertificates.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()));

classpathCertificates = new ClasspathCertificates();
LOGGER.log(FINE, String.format("Loaded classpath certificates: %s.", classpathCertificates.getAliases()));
Expand All @@ -168,6 +174,15 @@ Long getRefreshInterval() {
.orElse(0L);
}

Set<String> getKeyVaultCertificateAliasFilterPatterns() {
return Optional.ofNullable(System.getProperty(CERTIFICATE_ALIAS_FILTER_PATTERNS_PROPERTY))
.map(value -> Stream.of(value.split(","))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just check the comma and semicolon can be used as the file name, I couldn't find a good delimiter.
Image
If sill using the comma in this property, please add a note in doc to tell users not using a alila including a comma.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another way: Use the number keys: azure.keyvault.jca.certificate-alias-filter-pattern for a single item, azure.keyvault.jca.certificate-alias-filter-pattern.xxx for multiple items, like bellow:

-Dazure.keyvault.jca.certificate-alias-filter-pattern.1=abc.*
-Dazure.keyvault.jca.certificate-alias-filter-pattern.2=test.*
-Dazure.keyvault.jca.certificate-alias-filter-pattern.3=\d{1,5}
Properties props = System.getProperties();

List<String> patterns =
    props.stringPropertyNames()
         .stream()
         .filter(k -> k.startsWith("azure.keyvault.jca.certificate-alias-filter-patterns."))
         .sorted()
         .map(props::getProperty)
         .toList();

[ "abc.*", "test.*", "\\d{1,5}" ]

.map(String::trim)
.filter(pattern -> !pattern.isEmpty())
.collect(Collectors.toSet()))
.orElse(Collections.emptySet());
}

/**
* get key vault key store by system property
*
Expand Down Expand Up @@ -254,16 +269,22 @@ public boolean engineEntryInstanceOf(String alias, Class<? extends KeyStore.Entr
*/
@Override
public Certificate engineGetCertificate(String alias) {
Certificate certificate = allCertificates.stream()
.map(AzureCertificates::getCertificates)
.filter(a -> a.containsKey(alias))
.findFirst()
.map(certificates -> certificates.get(alias))
.orElse(null);
Certificate certificate = null;
for (AzureCertificates certificatesSource : allCertificates) {
if (certificatesSource instanceof KeyVaultCertificates) {
certificate = ((KeyVaultCertificates) certificatesSource).getCertificate(alias);
} else {
certificate = certificatesSource.getCertificates().get(alias);
}

if (certificate != null) {
break;
}
}

if (refreshCertificatesWhenHaveUnTrustCertificate && certificate == null) {
keyVaultCertificates.refreshCertificates();
certificate = keyVaultCertificates.getCertificates().get(alias);
certificate = keyVaultCertificates.getCertificate(alias);
}
Comment thread
rujche marked this conversation as resolved.

return certificate;
Expand All @@ -283,7 +304,7 @@ public String engineGetCertificateAlias(Certificate cert) {
List<String> aliasList = getAllAliases();
for (String candidateAlias : aliasList) {
Certificate certificate = engineGetCertificate(candidateAlias);
if (certificate.equals(cert)) {
if (certificate != null && certificate.equals(cert)) {
alias = candidateAlias;
break;
}
Expand All @@ -307,16 +328,23 @@ public String engineGetCertificateAlias(Certificate cert) {
*/
@Override
public Certificate[] engineGetCertificateChain(String alias) {
Certificate[] certificates = allCertificates.stream()
.map(AzureCertificates::getCertificateChains)
.filter(Objects::nonNull)
.filter(a -> a.containsKey(alias))
.findFirst()
.map(m -> m.get(alias))
.orElse(null);
Certificate[] certificates = null;
for (AzureCertificates certificatesSource : allCertificates) {
if (certificatesSource instanceof KeyVaultCertificates) {
certificates = ((KeyVaultCertificates) certificatesSource).getCertificateChain(alias);
} else {
Certificate[] certificateChain = certificatesSource.getCertificateChains().get(alias);
certificates = certificateChain == null ? null : certificateChain.clone();
}

if (certificates != null) {
break;
}
}

if (refreshCertificatesWhenHaveUnTrustCertificate && certificates == null) {
keyVaultCertificates.refreshCertificates();
return keyVaultCertificates.getCertificateChains().get(alias);
return keyVaultCertificates.getCertificateChain(alias);
}
return certificates;
}
Comment thread
rujche marked this conversation as resolved.
Expand Down Expand Up @@ -358,12 +386,20 @@ public KeyStore.Entry engineGetEntry(String alias, KeyStore.ProtectionParameter
*/
@Override
public Key engineGetKey(String alias, char[] password) {
return allCertificates.stream()
.map(AzureCertificates::getCertificateKeys)
.filter(a -> a.containsKey(alias))
.findFirst()
.map(certificateKeys -> certificateKeys.get(alias))
.orElse(null);
Key key = null;
for (AzureCertificates certificatesSource : allCertificates) {
if (certificatesSource instanceof KeyVaultCertificates) {
key = ((KeyVaultCertificates) certificatesSource).getCertificateKey(alias);
} else {
key = certificatesSource.getCertificateKeys().get(alias);
}

if (key != null) {
break;
}
}

return key;
}

/**
Expand Down
Loading
Loading