Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5574853
Update source to remove a few dependencies
alzimmermsft Feb 5, 2026
b2957d0
Merge branch 'main' into AzKv_SimplifyKeyVaultJca
alzimmermsft Feb 6, 2026
607ca6f
Finalize work
alzimmermsft Feb 6, 2026
8c9c560
Finalize work
alzimmermsft Feb 6, 2026
32fa6fd
Remove Mockito
alzimmermsft Feb 6, 2026
b029021
Merge in upstream
alzimmermsft Jul 31, 2026
19ff957
Merge in main and resolve merge conflict
alzimmermsft Aug 5, 2026
2feddfb
Removed Mockito and related dependencies from POM
vcolin7 Aug 8, 2026
02b1bad
Merge remote-tracking branch 'origin/main' into AzKv_SimplifyKeyVaultJca
vcolin7 Aug 8, 2026
33caba1
Fixed compilation issue
vcolin7 Aug 8, 2026
7bf40ce
Ran Spotless
vcolin7 Aug 10, 2026
497419e
Merge remote-tracking branch 'origin/main' into AzKv_SimplifyKeyVaultJca
vcolin7 Aug 27, 2026
46f542d
More changes after merge
vcolin7 Aug 27, 2026
60ace7a
Reverted some changes
vcolin7 Aug 27, 2026
455beae
Added a few comments
vcolin7 Aug 27, 2026
fd7c858
Reverted formatting merge changes
vcolin7 Aug 27, 2026
d18315e
Updated samples to use HttpUrlConnection
vcolin7 Aug 28, 2026
5d3a43d
Fix JCA HTTP migration tests
vcolin7 Sep 1, 2026
9abdb60
Clean up JCA HTTP migration dependencies
vcolin7 Sep 1, 2026
0fe91fd
Merge branch 'main' into AzKv_SimplifyKeyVaultJca
vcolin7 Sep 1, 2026
21b5b61
Harden JCA HTTP error handling
vcolin7 Sep 1, 2026
732edd4
Merge branch 'main' into AzKv_SimplifyKeyVaultJca
vcolin7 Sep 1, 2026
6fc8f70
Fix JCA HTTPS hostname verification isolation
moarychan Sep 2, 2026
24457d9
Preserve non-fatal JCA AIA download failures
moarychan Sep 2, 2026
8d7e8c0
Fail closed when JCA TLS trust initialization fails
moarychan Sep 2, 2026
80caba9
Restore JCA response compression support
moarychan Sep 2, 2026
f102daa
Expose JCA binary HTTP response metadata
moarychan Sep 2, 2026
986ce56
Add real JCA HTTP connection tests
moarychan Sep 3, 2026
926d0fd
Document JCA proxy configuration
moarychan Sep 3, 2026
6f3a541
Fix spelling errors
moarychan Sep 3, 2026
5db80b1
Merge branch 'main' of github.com:Azure/azure-sdk-for-java into moary…
moarychan Sep 3, 2026
e375804
Ensure parameter is non-null in KeyVaultCertificates
moarychan Sep 3, 2026
9a8b0a9
Merge branch 'moarychan/replace-apache-http5-to-jdk-http-conn' of git…
moarychan Sep 3, 2026
d5162ab
Merge branch 'main' of github.com:Azure/azure-sdk-for-java into moary…
moarychan Sep 3, 2026
c0313f5
Clarify JCA proxy configuration
moarychan Sep 3, 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
1 change: 1 addition & 0 deletions sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
### 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. 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))
- Replaced Apache HttpClient 5 with the JDK `HttpURLConnection`, removing the Apache HttpClient and SLF4J runtime dependencies while preserving strict HTTPS hostname verification, non-fatal AIA download failures, fail-closed TLS trust initialization, and automatic gzip and deflate response decompression.

## 2.12.0 (2026-07-24)

Expand Down
271 changes: 222 additions & 49 deletions sdk/keyvault/azure-security-keyvault-jca/README.md

Large diffs are not rendered by default.

51 changes: 0 additions & 51 deletions sdk/keyvault/azure-security-keyvault-jca/pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
Expand Down Expand Up @@ -35,12 +34,6 @@
<version>2.73.11</version> <!-- {x-version-update;org.bouncycastle:bcpkix-lts8on;external_dependency} -->
<optional>true</optional>
</dependency>
<!-- Apache HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.4.3</version> <!-- {x-version-update;org.apache.httpcomponents.client5:httpclient5;external_dependency} -->
</dependency>
<!-- Conscrypt -->
<dependency>
<groupId>org.conscrypt</groupId>
Expand All @@ -55,40 +48,6 @@
<version>1.5.1</version> <!-- {x-version-update;com.azure:azure-json;dependency} -->
<optional>true</optional>
</dependency>
<!-- SLF4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.36</version> <!-- {x-version-update;org.slf4j:slf4j-nop;external_dependency} -->
</dependency>
<!-- Tests -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.11.0</version> <!-- {x-version-update;org.mockito:mockito-inline;external_dependency} -->
<scope>test</scope>
</dependency>
<!-- bytebuddy dependencies are required for mockito 4.11.0 to work with Java 21. Mockito 4.11.0 is the last release -->
<!-- of Mockito supporting Java 8 as a baseline. -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.18.11</version> <!-- {x-version-update;testdep_net.bytebuddy:byte-buddy;external_dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.18.11</version> <!-- {x-version-update;testdep_net.bytebuddy:byte-buddy-agent;external_dependency} -->
<scope>test</scope>
</dependency>
<!-- For some reason upgrading past Mockito 4.6.1 requires this to be added. -->
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>4.8.3</version> <!-- {x-version-update;com.github.spotbugs:spotbugs-annotations;external_dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core</artifactId>
Expand Down Expand Up @@ -218,14 +177,6 @@
<pattern>com.azure.json</pattern>
<shadedPattern>com.azure.security.keyvault.jca.implementation.shaded.com.azure.json</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.hc</pattern>
<shadedPattern>com.azure.security.keyvault.jca.implementation.shaded.org.apache.hc</shadedPattern>
</relocation>
<relocation>
<pattern>org.slf4j</pattern>
<shadedPattern>com.azure.security.keyvault.jca.implementation.shaded.org.slf4j</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
Expand Down Expand Up @@ -266,8 +217,6 @@
<includes>
<include>org.bouncycastle:bcpkix-lts8on:[2.73.11]</include> <!-- {x-include-update;org.bouncycastle:bcpkix-lts8on;external_dependency} -->
<include>org.conscrypt:conscrypt-openjdk-uber:[2.5.2]</include> <!-- {x-include-update;org.conscrypt:conscrypt-openjdk-uber;external_dependency} -->
<include>org.apache.httpcomponents.client5:httpclient5:[5.4.3]</include> <!-- {x-include-update;org.apache.httpcomponents.client5:httpclient5;external_dependency} -->
<include>org.slf4j:slf4j-nop:[1.7.36]</include> <!-- {x-include-update;org.slf4j:slf4j-nop;external_dependency} -->
</includes>
</bannedDependencies>
</rules>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@

package com.azure.security.keyvault.jca;

import com.azure.security.keyvault.jca.implementation.signature.KeyVaultKeylessRsa256Signature;
import com.azure.security.keyvault.jca.implementation.signature.KeyVaultKeylessRsa512Signature;
import com.azure.security.keyvault.jca.implementation.signature.KeyVaultKeylessEcSha256Signature;
import com.azure.security.keyvault.jca.implementation.signature.KeyVaultKeylessEcSha384Signature;
import com.azure.security.keyvault.jca.implementation.signature.KeyVaultKeylessEcSha512Signature;
import com.azure.security.keyvault.jca.implementation.signature.KeyVaultKeylessEcSha256Signature;
import com.azure.security.keyvault.jca.implementation.signature.AbstractKeyVaultKeylessSignature;
import com.azure.security.keyvault.jca.implementation.signature.KeyVaultKeylessRsa256Signature;
import com.azure.security.keyvault.jca.implementation.signature.KeyVaultKeylessRsa512Signature;
import com.azure.security.keyvault.jca.implementation.signature.KeyVaultKeylessRsaSsaPssSignature;

import java.lang.reflect.InvocationTargetException;
import java.security.PrivilegedAction;
import java.security.Provider;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Stream;

/**
* The Azure Key Vault security provider.
Expand Down Expand Up @@ -48,6 +45,7 @@ public final class KeyVaultJcaProvider extends Provider {
/**
* Constructor.
*/
@SuppressWarnings("deprecation")
public KeyVaultJcaProvider() {
super(PROVIDER_NAME, VERSION, INFO);
initialize();
Expand All @@ -74,21 +72,20 @@ private void initialize() {
Collections.singletonList("DKS"), null));
putService(new Provider.Service(this, "KeyStore", KeyVaultKeyStore.ALGORITHM_NAME,
KeyVaultKeyStore.class.getName(), Collections.singletonList(KeyVaultKeyStore.ALGORITHM_NAME), null));
Stream
.of(KeyVaultKeylessRsaSsaPssSignature.class, KeyVaultKeylessRsa256Signature.class,
KeyVaultKeylessRsa512Signature.class, KeyVaultKeylessEcSha256Signature.class,
KeyVaultKeylessEcSha384Signature.class, KeyVaultKeylessEcSha512Signature.class)
.forEach(c -> putService(new Service(this, "Signature", getAlgorithmName(c), c.getName(), null, null)));

putService(new Service(this, "Signature", KeyVaultKeylessRsaSsaPssSignature.ALGORITHM_NAME,
KeyVaultKeylessRsaSsaPssSignature.class.getName(), null, null));
putService(new Service(this, "Signature", KeyVaultKeylessRsa256Signature.ALGORITHM_NAME,
KeyVaultKeylessRsa256Signature.class.getName(), null, null));
putService(new Service(this, "Signature", KeyVaultKeylessRsa512Signature.ALGORITHM_NAME,
KeyVaultKeylessRsa512Signature.class.getName(), null, null));
putService(new Service(this, "Signature", KeyVaultKeylessEcSha256Signature.ALGORITHM_NAME,
KeyVaultKeylessEcSha256Signature.class.getName(), null, null));
putService(new Service(this, "Signature", KeyVaultKeylessEcSha384Signature.ALGORITHM_NAME,
KeyVaultKeylessEcSha384Signature.class.getName(), null, null));
putService(new Service(this, "Signature", KeyVaultKeylessEcSha512Signature.ALGORITHM_NAME,
KeyVaultKeylessEcSha512Signature.class.getName(), null, null));
return null;
});
}

private String getAlgorithmName(Class<? extends AbstractKeyVaultKeylessSignature> c) {
try {
return c.getDeclaredConstructor().newInstance().getAlgorithmName();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException
| NoSuchMethodException e) {
return "";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public final class KeyVaultTrustManagerFactoryProvider extends Provider {
/**
* Constructor.
*/
@SuppressWarnings("deprecation")
public KeyVaultTrustManagerFactoryProvider() {
super(NAME, VERSION, INFO);
initialize();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -251,19 +251,19 @@ private AccessToken obtainAccessToken() {
managedIdentity = URLEncoder.encode(managedIdentity, "UTF-8");
}

// Priority: 1. Service Principal (Client ID/Secret), 2. Workload Identity, 3. Managed Identity, 4. Provided Access Token
// Priority: 1. Service Principal, 2. Workload Identity, 3. User-assigned Managed Identity,
// 4. Provided Access Token, 5. System-assigned Managed Identity.
if (tenantId != null && clientId != null && clientSecret != null) {
LOGGER.info("Using client credentials (client ID/secret) for authentication");
String aadAuthenticationUri = getLoginUri(keyVaultUri + "certificates" + API_VERSION_POSTFIX,
disableChallengeResourceVerification);
result
= AccessTokenUtil.getAccessToken(resource, aadAuthenticationUri, tenantId, clientId, clientSecret);
result = getAccessToken(resource, aadAuthenticationUri, tenantId, clientId, clientSecret);
} else if (AccessTokenUtil.isWorkloadIdentityAvailable(clientId, tenantId)) {
LOGGER.info("Using workload identity for authentication");
result = AccessTokenUtil.getAccessTokenWithWorkloadIdentity(keyVaultBaseUri, tenantId, clientId);
} else if (managedIdentity != null) {
LOGGER.info("Using managed identity for authentication");
result = AccessTokenUtil.getAccessToken(resource, managedIdentity);
result = getAccessToken(resource, managedIdentity);
} else if (providedAccessToken != null && !providedAccessToken.isEmpty()) {
LOGGER.info("Using provided access token for authentication");
// Create an AccessToken object from the provided token string
Expand All @@ -273,7 +273,7 @@ private AccessToken obtainAccessToken() {
result = new AccessToken(providedAccessToken, Long.MAX_VALUE / 1000);
} else {
LOGGER.info("Using managed identity for authentication (default)");
result = AccessTokenUtil.getAccessToken(resource, null);
result = getAccessToken(resource, null);
}
} catch (UnsupportedEncodingException e) {
LOGGER.log(WARNING, "Could not obtain access token to authenticate with.", e);
Expand All @@ -292,15 +292,12 @@ private AccessToken obtainAccessToken() {
public List<String> getAliases() {
LOGGER.entering("KeyVaultClient", "getAliases");

ArrayList<String> result = new ArrayList<>();
HashMap<String, String> headers = new HashMap<>();

headers.put("Authorization", "Bearer " + getAccessToken());

List<String> result = new ArrayList<>();
Map<String, String> headers = Collections.singletonMap("Authorization", "Bearer " + getAccessToken());
String uri = keyVaultUri + "certificates" + API_VERSION_POSTFIX;

while (uri != null && !uri.isEmpty()) {
String response = HttpUtil.get(uri, headers);
String response = httpGet(uri, headers);
CertificateListResult certificateListResult = null;

if (response != null) {
Expand Down Expand Up @@ -346,12 +343,8 @@ private CertificateBundle getCertificateBundle(String alias) {
LOGGER.entering("KeyVaultClient", "getCertificateBundle", alias);

CertificateBundle result = null;
HashMap<String, String> headers = new HashMap<>();

headers.put("Authorization", "Bearer " + getAccessToken());

String uri = keyVaultUri + "certificates/" + alias + API_VERSION_POSTFIX;
String response = HttpUtil.get(uri, headers);
String response = httpGet(keyVaultUri + "certificates/" + alias + API_VERSION_POSTFIX,
Collections.singletonMap("Authorization", "Bearer " + getAccessToken()));

if (response != null) {
try {
Expand Down Expand Up @@ -466,11 +459,8 @@ public Certificate[] getCertificateChainForVersion(CertificateVersion certificat
return new Certificate[0];
}

HashMap<String, String> headers = new HashMap<>();

headers.put("Authorization", "Bearer " + getAccessToken());

String response = HttpUtil.get(certificateVersion.getSecretId() + API_VERSION_POSTFIX, headers);
String response = httpGet(certificateVersion.getSecretId() + API_VERSION_POSTFIX,
Collections.singletonMap("Authorization", "Bearer " + getAccessToken()));

if (response == null) {
throw new IllegalStateException("Failed to load certificate chain response for alias: " + alias);
Expand Down Expand Up @@ -533,7 +523,8 @@ public Key getKeyForVersion(CertificateVersion certificateVersion, char[] passwo

if (!exportable) {
// Keyless signing uses the versioned key ID instead of exporting private key material.
String keyAlgorithm = keyType.contains("-HSM") ? keyType.substring(0, keyType.indexOf("-HSM")) : keyType;
String keyAlgorithm
= keyType != null && keyType.contains("-HSM") ? keyType.substring(0, keyType.indexOf("-HSM")) : keyType;

KeyVaultPrivateKey key = Optional.ofNullable(certificateVersion.getKeyId())
.map(keyId -> new KeyVaultPrivateKey(keyAlgorithm, keyId, this))
Expand All @@ -548,11 +539,9 @@ public Key getKeyForVersion(CertificateVersion certificateVersion, char[] passwo
if (certificateSecretUri == null) {
return null;
}
Map<String, String> headers = new HashMap<>();

headers.put("Authorization", "Bearer " + getAccessToken());

String body = HttpUtil.get(certificateSecretUri + API_VERSION_POSTFIX, headers);
String body = httpGet(certificateSecretUri + API_VERSION_POSTFIX,
Collections.singletonMap("Authorization", "Bearer " + getAccessToken()));

if (body == null) {
// If the private key is not available the certificate cannot be used for server side certificates or mTLS.
Expand Down Expand Up @@ -621,13 +610,10 @@ public byte[] getSignedWithPrivateKey(String digestName, String digestValue, Str
LOGGER.entering("KeyVaultClient", "getSignedWithPrivateKey", new Object[] { digestName, digestValue, keyId });

SignResult result = null;
String bodyString = String.format("{\"alg\": \"" + digestName + "\", \"value\": \"%s\"}", digestValue);
Map<String, String> headers = new HashMap<>();

headers.put("Authorization", "Bearer " + getAccessToken());

String bodyString = "{\"alg\": \"" + digestName + "\", \"value\": \"" + digestValue + "\"}";
Map<String, String> headers = Collections.singletonMap("Authorization", "Bearer " + getAccessToken());
String uri = keyId + "/sign" + API_VERSION_POSTFIX;
String response = HttpUtil.post(uri, headers, bodyString, "application/json");
String response = httpPost(uri, headers, bodyString);

if (response != null) {
try {
Expand Down Expand Up @@ -702,4 +688,21 @@ private PrivateKey createPrivateKeyFromPem(String pemString, String keyType)

return privateKey;
}

String httpGet(String uri, Map<String, String> headers) {
return HttpUtil.get(uri, headers);
}

String httpPost(String uri, Map<String, String> headers, String body) {
return HttpUtil.post(uri, headers, body, "application/json");
}

AccessToken getAccessToken(String resource, String identity) {
return AccessTokenUtil.getAccessToken(resource, identity);
}

AccessToken getAccessToken(String resource, String aadAuthenticationUri, String tenantId, String clientId,
String clientSecret) {
return AccessTokenUtil.getAccessToken(resource, aadAuthenticationUri, tenantId, clientId, clientSecret);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public void deleteEntry(String alias) {
*/
public void loadCertificatesFromClasspath() {
try {
String[] filenames = getFilenames("/keyvault");
String[] filenames = getFilenames();
for (String filename : filenames) {
try (InputStream inputStream = getClass().getResourceAsStream("/keyvault/" + filename)) {
String alias = filename;
Expand Down Expand Up @@ -147,13 +147,12 @@ public void loadCertificatesFromClasspath() {
/**
* Get the filenames.
*
* @param path the path.
* @return the filenames.
* @throws IOException when an I/O error occurs.
*/
private String[] getFilenames(String path) throws IOException {
private String[] getFilenames() throws IOException {
List<String> filenames = new ArrayList<>();
try (InputStream in = getClass().getResourceAsStream(path)) {
try (InputStream in = getClass().getResourceAsStream("/keyvault")) {
if (!Objects.isNull(in)) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
String resource;
Expand Down
Loading