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
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.

## 2.12.0 (2026-07-24)

Expand Down
230 changes: 181 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