Skip to content

Commit 51525d7

Browse files
rohitsinghal4usinghalrohit4uCopilot
authored
Refresh filesystem certificates when loading the Key Vault JCA keystore (#50872)
Co-authored-by: Rohit Singhal <singhalrohit@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 50c204e commit 51525d7

5 files changed

Lines changed: 347 additions & 45 deletions

File tree

‎sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
### Bugs Fixed
1010

11+
- Reload filesystem certificates from the configured custom and well-known directories when a keystore is constructed or loaded, without sharing mutable filesystem certificate state between keystores.
12+
1113
### Other Changes
1214

1315
## 2.13.0 (2026-10-13)

‎sdk/keyvault/azure-security-keyvault-jca/README.md‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -556,11 +556,20 @@ You can load the certificate in the file system as a trusted certificate by conf
556556

557557
| Certificate Type | Description | Usage |
558558
|------------------------|---------------------------------------------|-------------------------------------------------------------------------------------------|
559-
| Well-Known Certificate | The file path to the well-known certificate | `System.setProperty("azure.cert-path.well-known", "<well-known-certificate-file-path>")]` |
560-
| Custom Certificate | The file path to the custom certificate | `System.setProperty("azure.cert-path.custom", "<custom-certificate-file-path>")` |
559+
| Well-Known Certificate | The directory containing well-known certificates | `System.setProperty("azure.cert-path.well-known", "<well-known-certificate-directory>")` |
560+
| Custom Certificate | The directory containing custom certificates | `System.setProperty("azure.cert-path.custom", "<custom-certificate-directory>")` |
561561

562562
Note: These properties support certificate files only (e.g., `.cer`, `.pem`, `.der`, `.crt`). Private keys are not supported.
563563

564+
Each keystore loads its own snapshot of these directories when it is constructed and whenever either `KeyStore.load(...)`
565+
overload is called. Added, replaced, and removed certificate files are reflected after the next load. Calling
566+
`load(null, null)` or `load((KeyStore.LoadStoreParameter) null)` retains the keystore's configured paths, including paths
567+
previously supplied through `KeyVaultLoadStoreParameter`; it does not reread system properties.
568+
569+
After changing certificate files, reload the keystore and rebuild the trust managers and `SSLContext` that use it.
570+
Existing trust managers, TLS connections, and TLS sessions are not automatically refreshed. Directory changes are not
571+
watched, and the Key Vault certificate refresh interval does not reload these filesystem sources.
572+
564573
### Key-Less certificates
565574
You can set the private key as [non-exportable] to ensure the security of the key.
566575

@@ -915,4 +924,3 @@ This project has adopted the [Microsoft Open Source Code of Conduct][microsoft_c
915924
[jca_reference_guide]: https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html
916925
[microsoft_code_of_conduct]: https://opensource.microsoft.com/codeofconduct/
917926
[non-exportable]: https://learn.microsoft.com/azure/key-vault/certificates/about-certificates#exportable-or-non-exportable-key
918-

‎sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultKeyStore.java‎

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@
2424
import java.util.Collections;
2525
import java.util.Date;
2626
import java.util.Enumeration;
27-
import java.util.HashMap;
2827
import java.util.List;
29-
import java.util.Map;
3028
import java.util.logging.Logger;
3129

3230
import static java.util.logging.Level.FINE;
@@ -61,16 +59,6 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
6159
*/
6260
private final JreCertificates jreCertificates;
6361

64-
/**
65-
* Store well Know certificates loaded from specific path.
66-
*/
67-
private SpecificPathCertificates wellKnowCertificates;
68-
69-
/**
70-
* Store custom certificates loaded from specific path.
71-
*/
72-
private SpecificPathCertificates customCertificates;
73-
7462
/**
7563
* Store certificates loaded from KeyVault.
7664
*/
@@ -84,7 +72,7 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
8472
/**
8573
* Stores all the certificates.
8674
*/
87-
private List<AzureCertificates> allCertificates;
75+
private volatile List<AzureCertificates> allCertificates;
8876

8977
/**
9078
* Stores the creation date.
@@ -142,12 +130,19 @@ private void updateKeyStoreConfiguration(KeyVaultLoadStoreParameter parameter) {
142130
certPathWellKnown = parameter.getCertPathWellKnown();
143131
certPathCustom = parameter.getCertPathCustom();
144132

145-
wellKnowCertificates = SpecificPathCertificates.getSpecificPathCertificates(certPathWellKnown);
133+
loadCertificatesFromSpecificPaths();
134+
}
135+
136+
private void loadCertificatesFromSpecificPaths() {
137+
SpecificPathCertificates wellKnowCertificates
138+
= SpecificPathCertificates.getSpecificPathCertificates(certPathWellKnown);
146139
LOGGER.log(FINE, String.format("Loaded well known certificates: %s.", wellKnowCertificates.getAliases()));
147140

148-
customCertificates = SpecificPathCertificates.getSpecificPathCertificates(certPathCustom);
141+
SpecificPathCertificates customCertificates
142+
= SpecificPathCertificates.getSpecificPathCertificates(certPathCustom);
149143
LOGGER.log(FINE, String.format("Loaded custom certificates: %s.", customCertificates.getAliases()));
150144

145+
// Publish both filesystem snapshots together without modifying sources retained by other keystores.
151146
allCertificates = Arrays.asList(jreCertificates, wellKnowCertificates, customCertificates, keyVaultCertificates,
152147
classpathCertificates);
153148
}
@@ -386,7 +381,9 @@ public boolean engineIsKeyEntry(String alias) {
386381

387382
/**
388383
* Loads the keystore using the given {@code KeyStore.LoadStoreParameter}.
389-
* A {@link KeyVaultLoadStoreParameter} replaces the complete configuration captured by the constructor.
384+
* A {@link KeyVaultLoadStoreParameter} replaces the complete configuration captured by the constructor.
385+
* Reloads certificates from the configured filesystem paths. Existing trust managers and SSL contexts
386+
* must be rebuilt to use the updated certificates.
390387
*
391388
* @param param the {@code KeyStore.LoadStoreParameter}
392389
* that specifies how to load the keystore,
@@ -399,38 +396,39 @@ public void engineLoad(KeyStore.LoadStoreParameter param) {
399396

400397
keyVaultCertificates.updateKeyVaultClient(parameter);
401398
updateKeyStoreConfiguration(parameter);
399+
} else {
400+
loadCertificatesFromSpecificPaths();
402401
}
403402

404403
classpathCertificates.loadCertificatesFromClasspath();
405404
}
406405

407406
/**
408407
* Loads the keystore from the given input stream.
408+
* Reloads certificates from the configured filesystem paths without changing the configuration.
409+
* Existing trust managers and SSL contexts must be rebuilt to use the updated certificates.
409410
*
410411
* @param stream the input stream from which the keystore is loaded,or {@code null}
411412
* @param password the password
412413
*/
413414
@Override
414415
public void engineLoad(InputStream stream, char[] password) {
416+
loadCertificatesFromSpecificPaths();
415417
classpathCertificates.loadCertificatesFromClasspath();
416418
}
417419

418420
private List<String> getAllAliases() {
419-
List<String> allAliases = new ArrayList<>(jreCertificates.getAliases());
420-
Map<String, List<String>> aliasLists = new HashMap<>();
421-
422-
aliasLists.put("well known certificates", wellKnowCertificates.getAliases());
423-
aliasLists.put("custom certificates", customCertificates.getAliases());
424-
aliasLists.put("key vault certificates", keyVaultCertificates.getAliases());
425-
aliasLists.put("class path certificates", classpathCertificates.getAliases());
426-
427-
aliasLists.forEach((certificatesType, certificates) -> certificates.forEach(alias -> {
428-
if (allAliases.contains(alias)) {
429-
LOGGER.log(FINE, String.format("The certificate %s under %s already exists", alias, certificatesType));
430-
} else {
431-
allAliases.add(alias);
421+
List<String> allAliases = new ArrayList<>();
422+
for (AzureCertificates certificatesSource : allCertificates) {
423+
for (String alias : certificatesSource.getAliases()) {
424+
if (allAliases.contains(alias)) {
425+
LOGGER.log(FINE, "The certificate {0} under {1} already exists",
426+
new Object[] { alias, certificatesSource.getClass().getSimpleName() });
427+
} else {
428+
allAliases.add(alias);
429+
}
432430
}
433-
}));
431+
}
434432

435433
return allAliases;
436434
}

‎sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/SpecificPathCertificates.java‎

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
*/
3232
public final class SpecificPathCertificates implements AzureCertificates {
3333

34-
private static final Map<String, SpecificPathCertificates> CACHE = new HashMap<>();
35-
3634
/**
3735
* Stores the logger.
3836
*/
@@ -211,17 +209,12 @@ private List<File> getFiles() {
211209
}
212210

213211
/**
214-
* Get File System certificates by path
212+
* Load a new snapshot of file system certificates from the given path.
215213
*
216-
* @param path certificate path, which works only in first time
217-
* @return file certificate
214+
* @param path certificate directory path
215+
* @return independently loaded file system certificates
218216
*/
219-
public static synchronized SpecificPathCertificates getSpecificPathCertificates(String path) {
220-
SpecificPathCertificates result = CACHE.getOrDefault(path, null);
221-
if (result == null) {
222-
result = new SpecificPathCertificates(path);
223-
CACHE.put(path, result);
224-
}
225-
return result;
217+
public static SpecificPathCertificates getSpecificPathCertificates(String path) {
218+
return new SpecificPathCertificates(path);
226219
}
227220
}

0 commit comments

Comments
 (0)