From d93bd354d8b94944e752581abd321f1f5329409f Mon Sep 17 00:00:00 2001 From: Vladimir Rusnak Date: Fri, 29 May 2026 22:27:10 +0200 Subject: [PATCH 1/3] Enable JVM proxy system properties in Key Vault JCA --- .../azure-security-keyvault-jca/README.md | 20 ++++- .../jca/implementation/utils/HttpUtil.java | 2 +- .../implementation/utils/HttpUtilTest.java | 76 +++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/README.md b/sdk/keyvault/azure-security-keyvault-jca/README.md index f31a7ca5e90a..b97fd8e111d0 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/README.md +++ b/sdk/keyvault/azure-security-keyvault-jca/README.md @@ -544,6 +544,24 @@ az role assignment create \ -J-Dazure.keyvault.client-secret=${CLIENT_SECRET} ``` +If you run `jarsigner` behind a proxy, pass the standard JVM proxy system properties with `-J`: + ```bash + jarsigner -keystore NONE -storetype AzureKeyVault \ + -signedjar signerjar.jar ${PARAM_YOUR_JAR_FILE_PATH} "${CERT_NAME}" \ + -verbose -storepass "" \ + -providerName AzureKeyVault \ + -providerClass com.azure.security.keyvault.jca.KeyVaultJcaProvider \ + -J-Dazure.keyvault.uri=${KEYVAULT_URL} \ + -J-Dazure.keyvault.tenant-id=${TENANT} \ + -J-Dazure.keyvault.client-id=${CLIENT_ID} \ + -J-Dazure.keyvault.client-secret=${CLIENT_SECRET} \ + -J-Dhttps.proxyHost=proxy.company.local \ + -J-Dhttps.proxyPort=8080 \ + '-J-Dhttp.nonProxyHosts=169.254.169.254|localhost|127.*' + ``` +`http.nonProxyHosts` may be needed for local or managed identity endpoints, such as `169.254.169.254`, +that should bypass the proxy. + replace ${PARAM_YOUR_JAR_FILE_PATH} with the path of your jar file, replace ${PARAM_JCA_PROVIDER_JAR_PATH} with the path of the jca provider jar. Check your output, if you see the `jar signed` message, it means the jar is signed successfully. @@ -681,5 +699,3 @@ This project has adopted the [Microsoft Open Source Code of Conduct][microsoft_c [jca_reference_guide]: https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html [microsoft_code_of_conduct]: https://opensource.microsoft.com/codeofconduct/ [non-exportable]: https://learn.microsoft.com/azure/key-vault/certificates/about-certificates#exportable-or-non-exportable-key - - diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtil.java index 4f4480cc0d52..0026709ccd69 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtil.java @@ -186,7 +186,7 @@ private static CloseableHttpClient buildClient() { .register("https", sslConnectionSocketFactory) .build()); - return HttpClients.custom().setConnectionManager(manager).build(); + return HttpClients.custom().useSystemProperties().setConnectionManager(manager).build(); } public static String validateUri(String uri, String propertyName) { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java index d50c99d7a68e..96bef275c43b 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java @@ -6,6 +6,16 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + import static com.azure.security.keyvault.jca.implementation.utils.HttpUtil.DEFAULT_USER_AGENT_VALUE_PREFIX; import static com.azure.security.keyvault.jca.implementation.utils.HttpUtil.VERSION; import static org.junit.jupiter.api.Assertions.*; @@ -18,6 +28,39 @@ public void getUserAgentPrefixTest() { assertEquals(DEFAULT_USER_AGENT_VALUE_PREFIX + VERSION, HttpUtil.USER_AGENT_VALUE); } + @Test + public void getUsesJvmProxySystemProperties() throws Exception { + String previousProxyHost = System.getProperty("http.proxyHost"); + String previousProxyPort = System.getProperty("http.proxyPort"); + String previousNonProxyHosts = System.getProperty("http.nonProxyHosts"); + + try (ServerSocket proxyServer = new ServerSocket(0)) { + CountDownLatch requestReceived = new CountDownLatch(1); + AtomicReference requestLine = new AtomicReference<>(); + AtomicReference proxyFailure = new AtomicReference<>(); + + Thread proxyThread + = new Thread(() -> handleProxyRequest(proxyServer, requestLine, requestReceived, proxyFailure)); + proxyThread.setDaemon(true); + proxyThread.start(); + + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", String.valueOf(proxyServer.getLocalPort())); + System.clearProperty("http.nonProxyHosts"); + + String response = HttpUtil.get("http://azure-keyvault-jca-proxy-test.invalid/path", null); + + assertTrue(requestReceived.await(5, TimeUnit.SECONDS), "Expected proxy server to receive the request."); + assertNull(proxyFailure.get(), "Proxy server failed while handling the request."); + assertEquals("proxied", response); + assertEquals("GET http://azure-keyvault-jca-proxy-test.invalid/path HTTP/1.1", requestLine.get()); + } finally { + restoreProperty("http.proxyHost", previousProxyHost); + restoreProperty("http.proxyPort", previousProxyPort); + restoreProperty("http.nonProxyHosts", previousNonProxyHosts); + } + } + @Test @Disabled("Disable this because it will cause pipeline failure: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=1196171&view=logs&j=4a83f3be-c53d-53dd-7954-86872056fb11&t=54174aae-5a55-579d-08e2-94fb446f7b77&l=29") public void testHttpUtilGet() { @@ -35,4 +78,37 @@ public void testHttpUtilGet1() { assertNotNull(result); assertFalse(result.isEmpty()); } + + private static void handleProxyRequest(ServerSocket proxyServer, AtomicReference requestLine, + CountDownLatch requestReceived, AtomicReference proxyFailure) { + try (Socket socket = proxyServer.accept(); + BufferedReader reader = new BufferedReader( + new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); + OutputStream outputStream = socket.getOutputStream()) { + + requestLine.set(reader.readLine()); + String line; + while ((line = reader.readLine()) != null && !line.isEmpty()) { + // Consume request headers before writing the response. + } + + byte[] body = "proxied".getBytes(StandardCharsets.UTF_8); + outputStream.write(("HTTP/1.1 200 OK\r\nContent-Length: " + body.length + "\r\n\r\n") + .getBytes(StandardCharsets.UTF_8)); + outputStream.write(body); + outputStream.flush(); + requestReceived.countDown(); + } catch (Exception e) { + proxyFailure.set(e); + requestReceived.countDown(); + } + } + + private static void restoreProperty(String name, String value) { + if (value == null) { + System.clearProperty(name); + } else { + System.setProperty(name, value); + } + } } From ce356fa4ddd7c6dd75facf92627d3640ae1f3052 Mon Sep 17 00:00:00 2001 From: Vladimir Rusnak Date: Fri, 29 May 2026 22:42:25 +0200 Subject: [PATCH 2/3] Harden Key Vault JCA proxy PR --- sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md | 3 +++ .../keyvault/jca/implementation/utils/HttpUtilTest.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md index 034766972361..90d533449de1 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md +++ b/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md @@ -8,6 +8,9 @@ ### Bugs Fixed +- Fixed an issue where the internal HTTP client did not honor JVM proxy system properties. + ([#28801](https://github.com/Azure/azure-sdk-for-java/issues/28801)) + ### Other Changes ## 2.11.0 (2026-02-28) diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java index 96bef275c43b..121ceadbfc0d 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java @@ -5,6 +5,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -29,6 +31,7 @@ public void getUserAgentPrefixTest() { } @Test + @ResourceLock(Resources.SYSTEM_PROPERTIES) public void getUsesJvmProxySystemProperties() throws Exception { String previousProxyHost = System.getProperty("http.proxyHost"); String previousProxyPort = System.getProperty("http.proxyPort"); From 67f983b5adbe76af736e07b59b157eeee42d8b64 Mon Sep 17 00:00:00 2001 From: Vladimir Rusnak Date: Fri, 29 May 2026 23:12:12 +0200 Subject: [PATCH 3/3] Fix Key Vault JCA CI formatting --- .../azure-security-keyvault-jca/README.md | 30 ++++++++++--------- .../implementation/utils/HttpUtilTest.java | 14 +++++---- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/README.md b/sdk/keyvault/azure-security-keyvault-jca/README.md index b97fd8e111d0..564cd1a9b172 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/README.md +++ b/sdk/keyvault/azure-security-keyvault-jca/README.md @@ -545,20 +545,22 @@ az role assignment create \ ``` If you run `jarsigner` behind a proxy, pass the standard JVM proxy system properties with `-J`: - ```bash - jarsigner -keystore NONE -storetype AzureKeyVault \ - -signedjar signerjar.jar ${PARAM_YOUR_JAR_FILE_PATH} "${CERT_NAME}" \ - -verbose -storepass "" \ - -providerName AzureKeyVault \ - -providerClass com.azure.security.keyvault.jca.KeyVaultJcaProvider \ - -J-Dazure.keyvault.uri=${KEYVAULT_URL} \ - -J-Dazure.keyvault.tenant-id=${TENANT} \ - -J-Dazure.keyvault.client-id=${CLIENT_ID} \ - -J-Dazure.keyvault.client-secret=${CLIENT_SECRET} \ - -J-Dhttps.proxyHost=proxy.company.local \ - -J-Dhttps.proxyPort=8080 \ - '-J-Dhttp.nonProxyHosts=169.254.169.254|localhost|127.*' - ``` + +```bash +jarsigner -keystore NONE -storetype AzureKeyVault \ + -signedjar signerjar.jar ${PARAM_YOUR_JAR_FILE_PATH} "${CERT_NAME}" \ + -verbose -storepass "" \ + -providerName AzureKeyVault \ + -providerClass com.azure.security.keyvault.jca.KeyVaultJcaProvider \ + -J-Dazure.keyvault.uri=${KEYVAULT_URL} \ + -J-Dazure.keyvault.tenant-id=${TENANT} \ + -J-Dazure.keyvault.client-id=${CLIENT_ID} \ + -J-Dazure.keyvault.client-secret=${CLIENT_SECRET} \ + -J-Dhttps.proxyHost=proxy.company.local \ + -J-Dhttps.proxyPort=8080 \ + '-J-Dhttp.nonProxyHosts=169.254.169.254|localhost|127.*' +``` + `http.nonProxyHosts` may be needed for local or managed identity endpoints, such as `169.254.169.254`, that should bypass the proxy. diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java index 121ceadbfc0d..96966ae613de 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java @@ -85,19 +85,21 @@ public void testHttpUtilGet1() { private static void handleProxyRequest(ServerSocket proxyServer, AtomicReference requestLine, CountDownLatch requestReceived, AtomicReference proxyFailure) { try (Socket socket = proxyServer.accept(); - BufferedReader reader = new BufferedReader( - new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); + BufferedReader reader + = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); OutputStream outputStream = socket.getOutputStream()) { requestLine.set(reader.readLine()); String line; - while ((line = reader.readLine()) != null && !line.isEmpty()) { - // Consume request headers before writing the response. + while ((line = reader.readLine()) != null) { + if (line.isEmpty()) { + break; + } } byte[] body = "proxied".getBytes(StandardCharsets.UTF_8); - outputStream.write(("HTTP/1.1 200 OK\r\nContent-Length: " + body.length + "\r\n\r\n") - .getBytes(StandardCharsets.UTF_8)); + outputStream.write( + ("HTTP/1.1 200 OK\r\nContent-Length: " + body.length + "\r\n\r\n").getBytes(StandardCharsets.UTF_8)); outputStream.write(body); outputStream.flush(); requestReceived.countDown();