From 55748531998918ab8750fe5f019e507c592c7d1b Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:13:51 -0500 Subject: [PATCH 01/16] Update source to remove a few dependencies --- .../azure-security-keyvault-jca/pom.xml | 25 --- .../keyvault/jca/KeyVaultJcaProvider.java | 37 ++-- .../KeyVaultTrustManagerFactoryProvider.java | 1 + .../jca/implementation/KeyVaultClient.java | 40 ++-- .../certificates/ClasspathCertificates.java | 7 +- .../SpecificPathCertificates.java | 14 +- .../KeyVaultKeylessEcSha256Signature.java | 6 +- .../KeyVaultKeylessEcSha384Signature.java | 6 +- .../KeyVaultKeylessEcSha512Signature.java | 6 +- .../KeyVaultKeylessRsa256Signature.java | 14 +- .../KeyVaultKeylessRsa512Signature.java | 14 +- .../KeyVaultKeylessRsaSignature.java | 1 - .../KeyVaultKeylessRsaSsaPssSignature.java | 14 +- .../implementation/utils/AccessTokenUtil.java | 70 +++--- .../implementation/utils/CertificateUtil.java | 7 +- .../jca/implementation/utils/HttpUtil.java | 203 +++++++++--------- .../utils/JsonConverterUtil.java | 28 +-- .../keyvault/jca/PropertyConvertorUtils.java | 13 +- .../JreKeyStoreFactoryTest.java | 4 +- .../jca/implementation/JreKeyStoreTest.java | 7 +- .../KeyVaultCertificatesTest.java | 4 +- .../SpecificPathCertificatesTest.java | 4 +- .../KeyVaultKeylessEcSignatureTest.java | 5 +- .../implementation/utils/HttpUtilTest.java | 4 +- .../utils/JsonConverterUtilTest.java | 2 +- 25 files changed, 240 insertions(+), 296 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/pom.xml b/sdk/keyvault/azure-security-keyvault-jca/pom.xml index bd54cea363c3..d480be761abe 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-jca/pom.xml @@ -35,18 +35,6 @@ 2.73.8 true - - - org.apache.httpcomponents.client5 - httpclient5 - 5.4.3 - - - org.brotli - dec - 0.1.2 - true - org.conscrypt @@ -88,19 +76,6 @@ 1.17.7 test - - - com.github.spotbugs - spotbugs-annotations - 4.8.3 - test - - - com.azure - azure-core - 1.57.1 - test - org.junit.jupiter diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultJcaProvider.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultJcaProvider.java index 99e70d955eab..bfd15e14c149 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultJcaProvider.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultJcaProvider.java @@ -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. @@ -48,6 +45,7 @@ public final class KeyVaultJcaProvider extends Provider { /** * Constructor. */ + @SuppressWarnings("deprecation") public KeyVaultJcaProvider() { super(PROVIDER_NAME, VERSION, INFO); initialize(); @@ -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 c) { - try { - return c.getDeclaredConstructor().newInstance().getAlgorithmName(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException - | NoSuchMethodException e) { - return ""; - } - } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManagerFactoryProvider.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManagerFactoryProvider.java index dafba85e114d..d911f502af88 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManagerFactoryProvider.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultTrustManagerFactoryProvider.java @@ -36,6 +36,7 @@ public final class KeyVaultTrustManagerFactoryProvider extends Provider { /** * Constructor. */ + @SuppressWarnings("deprecation") public KeyVaultTrustManagerFactoryProvider() { super(NAME, VERSION, INFO); initialize(); diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java index 045c69c52b68..725e3c999698 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java @@ -37,7 +37,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; @@ -258,11 +258,8 @@ private AccessToken obtainAccessToken() { public List getAliases() { LOGGER.entering("KeyVaultClient", "getAliases"); - ArrayList result = new ArrayList<>(); - HashMap headers = new HashMap<>(); - - headers.put("Authorization", "Bearer " + getAccessToken()); - + List result = new ArrayList<>(); + Map headers = Collections.singletonMap("Authorization", "Bearer " + getAccessToken()); String uri = keyVaultUri + "certificates" + API_VERSION_POSTFIX; while (uri != null && !uri.isEmpty()) { @@ -304,12 +301,8 @@ private CertificateBundle getCertificateBundle(String alias) { LOGGER.entering("KeyVaultClient", "getCertificateBundle", alias); CertificateBundle result = null; - HashMap headers = new HashMap<>(); - - headers.put("Authorization", "Bearer " + getAccessToken()); - - String uri = keyVaultUri + "certificates/" + alias + API_VERSION_POSTFIX; - String response = HttpUtil.get(uri, headers); + String response = HttpUtil.get(keyVaultUri + "certificates/" + alias + API_VERSION_POSTFIX, + Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (response != null) { try { @@ -368,12 +361,8 @@ public Certificate[] getCertificateChain(String alias) { LOGGER.entering("KeyVaultClient", "getCertificateChain", alias); LOGGER.log(INFO, "Getting certificate chain for alias: {0}", alias); - HashMap headers = new HashMap<>(); - - headers.put("Authorization", "Bearer " + getAccessToken()); - String uri = keyVaultUri + "secrets/" + alias + API_VERSION_POSTFIX; - String response = HttpUtil.get(uri, headers); + String response = HttpUtil.get(uri, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (response == null) { throw new NullPointerException(); @@ -429,7 +418,8 @@ public Key getKey(String alias, char[] password) { // Return KeyVaultPrivateKey if certificate is not exportable because if the service needs to obtain the // private key for authentication, and we can't access private key(which is not exportable), we will use // the Azure Key Vault Secrets API to obtain the private key (keyless). - String keyType2 = keyType.contains("-HSM") ? keyType.substring(0, keyType.indexOf("-HSM")) : keyType; + int index = keyType.indexOf("-HSM"); + String keyType2 = (index == -1) ? keyType : keyType.substring(0, index); KeyVaultPrivateKey key = Optional.ofNullable(certificateBundle) .map(CertificateBundle::getKid) @@ -441,12 +431,8 @@ public Key getKey(String alias, char[] password) { return key; } - String certificateSecretUri = certificateBundle.getSid(); - Map headers = new HashMap<>(); - - headers.put("Authorization", "Bearer " + getAccessToken()); - - String body = HttpUtil.get(certificateSecretUri + API_VERSION_POSTFIX, headers); + String body = HttpUtil.get(certificateBundle.getSid() + 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. @@ -515,10 +501,8 @@ 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 headers = new HashMap<>(); - - headers.put("Authorization", "Bearer " + getAccessToken()); + String bodyString = "{\"alg\": \"" + digestName + "\", \"value\": \"" + digestValue + "\"}"; + Map headers = Collections.singletonMap("Authorization", "Bearer " + getAccessToken()); String uri = keyId + "/sign" + API_VERSION_POSTFIX; String response = HttpUtil.post(uri, headers, bodyString, "application/json"); diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/ClasspathCertificates.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/ClasspathCertificates.java index 1714a3c0334f..b1758198be00 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/ClasspathCertificates.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/ClasspathCertificates.java @@ -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; @@ -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 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; diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/SpecificPathCertificates.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/SpecificPathCertificates.java index 30d66f230760..a68cbacf7e2f 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/SpecificPathCertificates.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/SpecificPathCertificates.java @@ -19,9 +19,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.logging.Logger; -import java.util.stream.Stream; import static com.azure.security.keyvault.jca.implementation.utils.CertificateUtil.loadX509CertificateFromFile; import static com.azure.security.keyvault.jca.implementation.utils.CertificateUtil.loadX509CertificatesFromFile; @@ -203,13 +201,11 @@ private List getFiles() { List files = new ArrayList<>(); File filePackage = new File(certificatePath); File[] array = filePackage.listFiles(); - Optional.ofNullable(array) - .map(Arrays::stream) - .orElseGet(Stream::empty) - .filter(Objects::nonNull) - .filter(File::isFile) - .filter(File::exists) - .filter(File::canRead) + if (array == null) { + return files; + } + Arrays.stream(array) + .filter(file -> Objects.nonNull(file) && file.exists() && file.isFile() && file.canRead()) .forEach(files::add); return files; } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha256Signature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha256Signature.java index a936e2722bdd..be8bf32deea7 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha256Signature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha256Signature.java @@ -6,10 +6,14 @@ * key vault SHA256 */ public final class KeyVaultKeylessEcSha256Signature extends KeyVaultKeylessEcSignature { + /** + * Algorithm name used by this implementation. + */ + public static String ALGORITHM_NAME = "SHA256withECDSA"; @Override public String getAlgorithmName() { - return "SHA256withECDSA"; + return ALGORITHM_NAME; } /** diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha384Signature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha384Signature.java index 0d267dfcd973..9e54533b6dac 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha384Signature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha384Signature.java @@ -6,10 +6,14 @@ * key vault SHA384 */ public final class KeyVaultKeylessEcSha384Signature extends KeyVaultKeylessEcSignature { + /** + * Algorithm name used by this implementation. + */ + public static String ALGORITHM_NAME = "SHA384withECDSA"; @Override public String getAlgorithmName() { - return "SHA384withECDSA"; + return ALGORITHM_NAME; } /** diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha512Signature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha512Signature.java index 23b9ff7b443e..a468501c1e18 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha512Signature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha512Signature.java @@ -6,10 +6,14 @@ * key vault SHA512 */ public final class KeyVaultKeylessEcSha512Signature extends KeyVaultKeylessEcSignature { + /** + * Algorithm name used by this implementation. + */ + public static String ALGORITHM_NAME = "SHA512withECDSA"; @Override public String getAlgorithmName() { - return "SHA512withECDSA"; + return ALGORITHM_NAME; } /** diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa256Signature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa256Signature.java index 3454bdb4716f..d73051353419 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa256Signature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa256Signature.java @@ -7,6 +7,15 @@ * key vault Rsa signature to support key less */ public class KeyVaultKeylessRsa256Signature extends KeyVaultKeylessRsaSignature { + /** + * Algorithm name used by this implementation. + */ + public static String ALGORITHM_NAME = "SHA256withRSA"; + + @Override + public String getAlgorithmName() { + return ALGORITHM_NAME; + } /** * Construct a new KeyVaultKeyLessRsaSignature @@ -14,9 +23,4 @@ public class KeyVaultKeylessRsa256Signature extends KeyVaultKeylessRsaSignature public KeyVaultKeylessRsa256Signature() { super("SHA-256", "RS256"); } - - @Override - public String getAlgorithmName() { - return "SHA256withRSA"; - } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa512Signature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa512Signature.java index 3115057ecbef..4d153405190a 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa512Signature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa512Signature.java @@ -7,6 +7,15 @@ * key vault Rsa signature to support key less */ public class KeyVaultKeylessRsa512Signature extends KeyVaultKeylessRsaSignature { + /** + * Algorithm name used by this implementation. + */ + public static String ALGORITHM_NAME = "SHA512withRSA"; + + @Override + public String getAlgorithmName() { + return ALGORITHM_NAME; + } /** * Construct a new KeyVaultKeyLessRsaSignature @@ -14,9 +23,4 @@ public class KeyVaultKeylessRsa512Signature extends KeyVaultKeylessRsaSignature public KeyVaultKeylessRsa512Signature() { super("SHA-512", "RS512"); } - - @Override - public String getAlgorithmName() { - return "SHA512withRSA"; - } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSignature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSignature.java index cc737a0750f0..83e56830a4ee 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSignature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSignature.java @@ -12,7 +12,6 @@ * key vault Rsa signature to support key less */ abstract class KeyVaultKeylessRsaSignature extends AbstractKeyVaultKeylessSignature { - private final String keyVaultDigestName; /** diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignature.java index d15df8aec525..0cd86b6bce05 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignature.java @@ -13,6 +13,15 @@ * key vault Rsa signature to support key less */ public class KeyVaultKeylessRsaSsaPssSignature extends KeyVaultKeylessRsaSignature { + /** + * Algorithm name used by this implementation. + */ + public static String ALGORITHM_NAME = "RSASSA-PSS"; + + @Override + public String getAlgorithmName() { + return ALGORITHM_NAME; + } /** * Construct a new KeyVaultKeyLessRsaSignature @@ -42,9 +51,4 @@ protected void engineSetParameter(AlgorithmParameterSpec params) throws InvalidA } } } - - @Override - public String getAlgorithmName() { - return "RSASSA-PSS"; - } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java index e7895c39958b..84ed43ff55aa 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java @@ -3,7 +3,6 @@ package com.azure.security.keyvault.jca.implementation.utils; import com.azure.security.keyvault.jca.implementation.model.AccessToken; -import org.apache.hc.core5.http.ClassicHttpResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -12,6 +11,7 @@ import java.net.URLEncoder; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Logger; @@ -91,8 +91,10 @@ public static AccessToken getAccessToken(String resource, String identity) { /* * App Service 2017-09-01: MSI_ENDPOINT, MSI_SECRET - * Azure Container App 2019-08-01: IDENTITY_ENDPOINT, IDENTITY_HEADER, see more from https://learn.microsoft.com/en-us/azure/container-apps/managed-identity?tabs=cli%2Chttp#rest-endpoint-reference - * Azure Virtual Machine 2018-02-01, see more from https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http + * Azure Container App 2019-08-01: IDENTITY_ENDPOINT, IDENTITY_HEADER, see more from + * https://learn.microsoft.com/azure/container-apps/managed-identity?tabs=cli%2Chttp#rest-endpoint-reference + * Azure Virtual Machine 2018-02-01, see more from + * https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http */ if (System.getenv("WEBSITE_SITE_NAME") != null && !System.getenv("WEBSITE_SITE_NAME").isEmpty()) { result = getAccessTokenOnAppService(resource, identity); @@ -125,15 +127,9 @@ public static AccessToken getAccessToken(String resource, String aadAuthenticati AccessToken result = null; - StringBuilder oauth2Url = new StringBuilder(); - - if (aadAuthenticationUrl == null) { - oauth2Url.append(OAUTH2_TOKEN_BASE_URL).append(tenantId).append("/"); - } else { - oauth2Url.append(addTrailingSlashIfRequired(aadAuthenticationUrl)); - } - - oauth2Url.append(OAUTH2_TOKEN_POSTFIX); + String oauth2Url = (aadAuthenticationUrl == null) + ? OAUTH2_TOKEN_BASE_URL + tenantId + "/" + OAUTH2_TOKEN_POSTFIX + : addTrailingSlashIfRequired(aadAuthenticationUrl) + OAUTH2_TOKEN_POSTFIX; String encodedClientSecret = ""; @@ -143,17 +139,10 @@ public static AccessToken getAccessToken(String resource, String aadAuthenticati LOGGER.log(WARNING, "Failed to encode client secret for access token request", e); } - StringBuilder requestBody = new StringBuilder(); - - requestBody.append(GRANT_TYPE_FRAGMENT) - .append(CLIENT_ID_FRAGMENT) - .append(clientId) - .append(CLIENT_SECRET_FRAGMENT) - .append(encodedClientSecret) - .append(RESOURCE_FRAGMENT) - .append(resource); + String requestBody = GRANT_TYPE_FRAGMENT + CLIENT_ID_FRAGMENT + clientId + CLIENT_SECRET_FRAGMENT + + encodedClientSecret + RESOURCE_FRAGMENT + resource; - String body = HttpUtil.post(oauth2Url.toString(), requestBody.toString(), "application/x-www-form-urlencoded"); + String body = HttpUtil.post(oauth2Url, null, requestBody, "application/x-www-form-urlencoded"); if (body != null) { try { @@ -180,17 +169,13 @@ private static AccessToken getAccessTokenOnAppService(String resource, String cl LOGGER.info("Getting access token using managed identity based on MSI_SECRET"); AccessToken result = null; - StringBuilder url = new StringBuilder(); - - url.append(System.getenv("MSI_ENDPOINT")) - .append("?api-version=2017-09-01") - .append(RESOURCE_FRAGMENT) - .append(resource); - + String url; if (clientId != null) { - url.append("&clientid=").append(clientId); - + url = System.getenv("MSI_ENDPOINT") + "?api-version=2017-09-01" + RESOURCE_FRAGMENT + resource + + "&clientid=" + clientId; LOGGER.log(INFO, "Using managed identity with client ID: {0}", clientId); + } else { + url = System.getenv("MSI_ENDPOINT") + "?api-version=2017-09-01" + RESOURCE_FRAGMENT + resource; } HashMap headers = new HashMap<>(); @@ -198,7 +183,7 @@ private static AccessToken getAccessTokenOnAppService(String resource, String cl headers.put("Metadata", "true"); headers.put("Secret", System.getenv("MSI_SECRET")); - String body = HttpUtil.get(url.toString(), headers); + String body = HttpUtil.get(url, headers); if (body != null) { try { @@ -237,9 +222,9 @@ private static AccessToken getAccessTokenOnContainerApp(String resource, String LOGGER.log(INFO, "Using managed identity with client ID: {0}", clientId); } - Map headers = new HashMap<>(); + Map headers = Collections.emptyMap(); if (System.getenv(PROPERTY_IDENTITY_HEADER) != null && !System.getenv(PROPERTY_IDENTITY_HEADER).isEmpty()) { - headers.put("X-IDENTITY-HEADER", System.getenv(PROPERTY_IDENTITY_HEADER)); + headers = Collections.singletonMap("X-IDENTITY-HEADER", System.getenv(PROPERTY_IDENTITY_HEADER)); } String body = HttpUtil.get(url.toString(), headers); @@ -282,11 +267,7 @@ private static AccessToken getAccessTokenOnOthers(String resource, String identi url.append("&object_id=").append(identity); } - HashMap headers = new HashMap<>(); - - headers.put("Metadata", "true"); - - String body = HttpUtil.get(url.toString(), headers); + String body = HttpUtil.get(url.toString(), Collections.singletonMap("Metadata", "true")); if (body != null) { try { @@ -305,14 +286,15 @@ public static String getLoginUri(String resourceUri, boolean disableChallengeRes LOGGER.entering("AccessTokenUtil", "getLoginUri", resourceUri); LOGGER.log(INFO, "Getting login URI using: {0}", resourceUri); - ClassicHttpResponse response = HttpUtil.getWithResponse(resourceUri, null); + Map> headers = HttpUtil.getWithResponseHeadersOnlyReturn(resourceUri); - if (response == null) { + if (headers == null) { throw new IllegalStateException("Could not obtain login URI to retrieve access token from."); } + List wwwAuthenticates = headers.get(WWW_AUTHENTICATE); Map challengeAttributes - = extractChallengeAttributes(response.getFirstHeader(WWW_AUTHENTICATE).getValue()); + = extractChallengeAttributes(wwwAuthenticates == null ? null : wwwAuthenticates.get(0)); String scope = challengeAttributes.get("resource"); if (scope != null) { @@ -373,7 +355,7 @@ private static Map extractChallengeAttributes(String authenticat for (String pair : attributes) { String[] keyValue = pair.split("="); - attributeMap.put(keyValue[0].replaceAll("\"", ""), keyValue[1].replaceAll("\"", "")); + attributeMap.put(keyValue[0].replace("\"", ""), keyValue[1].replace("\"", "")); } LOGGER.exiting("AccessTokenUtil", "extractChallengeAttributes", attributeMap); @@ -391,7 +373,7 @@ private static Map extractChallengeAttributes(String authenticat private static boolean isBearerChallenge(String authenticateHeader) { return authenticateHeader != null && !authenticateHeader.isEmpty() - && authenticateHeader.toLowerCase(Locale.ROOT).startsWith(BEARER_TOKEN_PREFIX.toLowerCase(Locale.ROOT)); + && BEARER_TOKEN_PREFIX.regionMatches(true, 0, authenticateHeader, 0, BEARER_TOKEN_PREFIX.length()); } /** diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/CertificateUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/CertificateUtil.java index 014513520a84..1e64d9dbb0ec 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/CertificateUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/CertificateUtil.java @@ -26,7 +26,6 @@ import java.util.ArrayList; import java.util.Base64; import java.util.List; -import java.util.stream.Collectors; public final class CertificateUtil { private static final String BEGIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----"; @@ -98,11 +97,7 @@ public static Certificate loadX509CertificateFromFile(InputStream inputStream) t public static Certificate[] loadX509CertificatesFromFile(InputStream inputStream) throws CertificateException { CertificateFactory factory = CertificateFactory.getInstance("X.509"); - return factory.generateCertificates(inputStream) - .stream() - .map(o -> (Certificate) o) - .collect(Collectors.toList()) - .toArray(new Certificate[0]); + return factory.generateCertificates(inputStream).toArray(new Certificate[0]); } public static String getCertificateNameFromCertificateItemId(String id) { 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..8bd2682d2ece 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 @@ -3,34 +3,25 @@ package com.azure.security.keyvault.jca.implementation.utils; import com.azure.security.keyvault.jca.implementation.JreKeyStoreFactory; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.client5.http.socket.ConnectionSocketFactory; -import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HttpEntity; -import org.apache.hc.core5.http.config.RegistryBuilder; -import org.apache.hc.core5.http.io.HttpClientResponseHandler; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.http.io.entity.StringEntity; -import org.apache.hc.core5.ssl.SSLContexts; - -import javax.net.ssl.HostnameVerifier; + +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; -import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; @@ -59,27 +50,26 @@ public final class HttpUtil { private static final Logger LOGGER = Logger.getLogger(HttpUtil.class.getName()); public static String get(String uri, Map headers) { - String result = null; - - try (CloseableHttpClient client = buildClient()) { - HttpGet httpGet = new HttpGet(uri); + HttpURLConnection connection = null; + try { + connection = openConnection(uri); + connection.setRequestMethod("GET"); + connection.setDoOutput(true); if (headers != null) { - headers.forEach(httpGet::addHeader); + headers.forEach(connection::setRequestProperty); } + connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); - httpGet.addHeader(USER_AGENT_KEY, USER_AGENT_VALUE); - - result = client.execute(httpGet, createResponseHandler()); + return readResponseBody(connection); } catch (IOException ioe) { LOGGER.log(WARNING, "Unable to finish the HTTP GET request.", ioe); + return null; + } finally { + if (connection != null) { + connection.disconnect(); + } } - - return result; - } - - public static String post(String uri, String body, String contentType) { - return post(uri, null, body, contentType); } public static String getUserAgentPrefix() { @@ -95,98 +85,105 @@ public static String getUserAgentPrefix() { } public static String post(String uri, Map headers, String body, String contentType) { - String result = null; - - try (CloseableHttpClient client = buildClient()) { - HttpPost httpPost = new HttpPost(uri); - - httpPost.addHeader(USER_AGENT_KEY, USER_AGENT_VALUE); + HttpURLConnection connection = null; + try { + connection = openConnection(uri); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); if (headers != null) { - headers.forEach(httpPost::addHeader); - httpPost.addHeader("Content-Type", contentType); + headers.forEach(connection::setRequestProperty); + } + if (contentType != null) { + connection.setRequestProperty("Content-Type", contentType); + } + connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); + try (OutputStream outputStream = connection.getOutputStream()) { + outputStream.write(body.getBytes(StandardCharsets.UTF_8)); } - httpPost.setEntity(new StringEntity(body, ContentType.create(contentType))); - - result = client.execute(httpPost, createResponseHandler()); + int status = connection.getResponseCode(); + if (status >= 200 && status < 300) { + return readResponseBody(connection); + } else { + LOGGER.log(SEVERE, createErrorMessage(status)); + return ""; + } } catch (IOException ioe) { LOGGER.log(WARNING, "Unable to finish the HTTP POST request.", ioe); + return null; + } finally { + if (connection != null) { + connection.disconnect(); + } } - - return result; } - public static ClassicHttpResponse getWithResponse(String uri, Map headers) { - ClassicHttpResponse result = null; - - try (CloseableHttpClient client = buildClient()) { - HttpGet httpGet = new HttpGet(uri); - - if (headers != null) { - headers.forEach(httpGet::addHeader); - } + private static String createErrorMessage(int status) { + return "Fail to get response from Key Vault because return http status code is " + status + ". It can be " + + "caused by missing permissions or roles. To know how to add permissions or roles, see " + + "https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/keyvault/azure-security-keyvault-jca#prerequisites."; + } - httpGet.addHeader(USER_AGENT_KEY, USER_AGENT_VALUE); + @SuppressWarnings("StringOperationCanBeSimplified") + private static String readResponseBody(HttpURLConnection connection) throws IOException { + InputStream responseBody + = (connection.getInputStream() != null) ? connection.getInputStream() : connection.getErrorStream(); - result = client.execute(httpGet, createResponseHandlerForAuthChallenge()); - } catch (IOException ioe) { - LOGGER.log(WARNING, "Unable to finish the HTTP GET request.", ioe); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int read; + while ((read = responseBody.read(buffer)) != -1) { + outputStream.write(buffer, 0, read); } - return result; + return new String(outputStream.toByteArray(), StandardCharsets.UTF_8); } - private static HttpClientResponseHandler createResponseHandler() { - return (ClassicHttpResponse response) -> { - int status = response.getCode(); - String result; + public static Map> getWithResponseHeadersOnlyReturn(String uri) { + HttpURLConnection connection = null; + try { + connection = openConnection(uri); + connection.setRequestMethod("GET"); + connection.setDoOutput(true); - if (status >= 200 && status < 300) { - HttpEntity entity = response.getEntity(); - result = entity != null ? EntityUtils.toString(entity) : null; + connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); + + if (connection.getResponseCode() == 401) { + return null; } else { - String errorMessage = "Fail to get response from Key Vault because return http status code is " + status - + ". It " - + "can be caused by missing permissions or roles. To know how to add permissions or roles, see " - + "https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/keyvault/azure-security-keyvault-jca#prerequisites."; - LOGGER.log(SEVERE, errorMessage); - throw new RuntimeException(errorMessage); + return connection.getHeaderFields(); } - - return result; - }; - } - - private static HttpClientResponseHandler createResponseHandlerForAuthChallenge() { - return (ClassicHttpResponse response) -> { - int status = response.getCode(); - - return status == 401 ? response : null; - }; + } catch (IOException ioe) { + LOGGER.log(WARNING, "Unable to finish the HTTP GET request.", ioe); + return null; + } finally { + if (connection != null) { + connection.disconnect(); + } + } } - private static CloseableHttpClient buildClient() { - KeyStore keyStore = JreKeyStoreFactory.getDefaultKeyStore(); - - SSLContext sslContext = null; - + private static HttpURLConnection openConnection(String uri) { try { - sslContext = SSLContexts.custom().loadTrustMaterial(keyStore, null).build(); - } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { - LOGGER.log(WARNING, "Unable to build the SSL context.", e); - } - - SSLConnectionSocketFactory sslConnectionSocketFactory - = new SSLConnectionSocketFactory(sslContext, (HostnameVerifier) null); - - PoolingHttpClientConnectionManager manager - = new PoolingHttpClientConnectionManager(RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslConnectionSocketFactory) - .build()); + HttpURLConnection connection = (HttpURLConnection) URI.create(uri).toURL().openConnection(); + if (connection instanceof HttpsURLConnection) { + try { + TrustManagerFactory trustManagerFactory + = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(JreKeyStoreFactory.getDefaultKeyStore()); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustManagerFactory.getTrustManagers(), null); + ((HttpsURLConnection) connection).setSSLSocketFactory(sslContext.getSocketFactory()); + } catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) { + LOGGER.log(WARNING, "Unable to build the SSL context.", e); + } + } - return HttpClients.custom().setConnectionManager(manager).build(); + return connection; + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } } public static String validateUri(String uri, String propertyName) { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtil.java index 74ebebed0b6b..e40d76a9aa29 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtil.java @@ -6,10 +6,8 @@ import com.azure.json.JsonProviders; import com.azure.json.JsonReader; import com.azure.json.JsonSerializable; -import com.azure.json.JsonWriter; import com.azure.json.ReadValueCallback; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.logging.Logger; @@ -55,27 +53,19 @@ public static > T fromJson(ReadValueCallback jsonSerializable) { LOGGER.entering("JsonConverterUtil", "toJson", jsonSerializable); - - if (jsonSerializable == null) { - return null; - } - - try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - JsonWriter jsonWriter = JsonProviders.createWriter(byteArrayOutputStream)) { - - jsonWriter.writeUntyped(jsonSerializable); - jsonWriter.flush(); - - return byteArrayOutputStream.toString("UTF-8"); - } catch (IOException e) { - LOGGER.log(WARNING, "Unable to convert to JSON", e); + String value = null; + + if (jsonSerializable != null) { + try { + value = jsonSerializable.toJsonString(); + } catch (IOException e) { + LOGGER.log(WARNING, "Unable to convert to JSON", e); + } } LOGGER.exiting("JsonConverterUtil", "toJson"); - - return null; + return value; } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/PropertyConvertorUtils.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/PropertyConvertorUtils.java index b3ca277b5c54..ea0ca7889286 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/PropertyConvertorUtils.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/PropertyConvertorUtils.java @@ -3,7 +3,6 @@ package com.azure.security.keyvault.jca; -import com.azure.core.util.Configuration; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -14,9 +13,6 @@ import java.util.List; public class PropertyConvertorUtils { - - private static final Configuration GLOBAL_CONFIGURATION = Configuration.getGlobalConfiguration(); - public static void putEnvironmentPropertyToSystemPropertyForKeyVaultJca() { KEYVAULT_JCA_SYSTEM_PROPERTIES.forEach(environmentPropertyKey -> { String value = getPropertyValue(environmentPropertyKey); @@ -41,11 +37,12 @@ public static void addKeyVaultJcaProvider() { } public static String getPropertyValue(String property) { - return GLOBAL_CONFIGURATION.get(property, System.getenv(property)); - } + String value = System.getProperty(property); + if (value != null) { + return value; + } - public static String getPropertyValue(String property, String defaultValue) { - return GLOBAL_CONFIGURATION.get(property, defaultValue); + return System.getenv(property); } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/JreKeyStoreFactoryTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/JreKeyStoreFactoryTest.java index d059a06792f9..21616730cc9e 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/JreKeyStoreFactoryTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/JreKeyStoreFactoryTest.java @@ -7,12 +7,12 @@ import java.security.KeyStore; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; public class JreKeyStoreFactoryTest { @Test public void test() { KeyStore jreKeyStore = JreKeyStoreFactory.getDefaultKeyStore(); - assertFalse(jreKeyStore.getType().equals(KeyVaultKeyStore.KEY_STORE_TYPE)); + assertNotEquals(KeyVaultKeyStore.KEY_STORE_TYPE, jreKeyStore.getType()); } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/JreKeyStoreTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/JreKeyStoreTest.java index cfa223e5073d..f8152bd1a1b0 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/JreKeyStoreTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/JreKeyStoreTest.java @@ -5,9 +5,12 @@ import com.azure.security.keyvault.jca.implementation.certificates.JreCertificates; import org.junit.jupiter.api.Test; + import java.security.cert.Certificate; import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class JreKeyStoreTest { @@ -17,7 +20,7 @@ public void testJreKsEntries() { assertNotNull(jreCertificates); assertNotNull(jreCertificates.getAliases()); Map certs = jreCertificates.getCertificates(); - assertTrue(certs.size() > 0); + assertFalse(certs.isEmpty()); assertNotNull(jreCertificates.getCertificateKeys()); } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java index 2791d1742e6a..e4dc803d6cc2 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java @@ -52,10 +52,10 @@ public void testGetCertificate() { @Test public void testRefreshAndGetAliasByCertificate() { - Assertions.assertEquals(keyVaultCertificates.refreshAndGetAliasByCertificate(certificate), "myalias"); + Assertions.assertEquals("myalias", keyVaultCertificates.refreshAndGetAliasByCertificate(certificate)); Assertions.assertEquals(keyVaultCertificates.getCertificates().get("myalias"), certificate); when(keyVaultClient.getAliases()).thenReturn(null); - Assertions.assertNotEquals(keyVaultCertificates.refreshAndGetAliasByCertificate(certificate), "myalias"); + Assertions.assertNotEquals("myalias", keyVaultCertificates.refreshAndGetAliasByCertificate(certificate)); Assertions.assertNull(keyVaultCertificates.getCertificates().get("myalias")); } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/SpecificPathCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/SpecificPathCertificatesTest.java index 3a038616a521..687ab4700736 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/SpecificPathCertificatesTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/SpecificPathCertificatesTest.java @@ -6,13 +6,15 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.nio.file.FileSystems; + public class SpecificPathCertificatesTest { SpecificPathCertificates specificPathCertificates; public static String getFilePath(String packageName) { String filepath = "\\src\\test\\resources\\" + packageName; - return System.getProperty("user.dir") + filepath.replace("\\", System.getProperty("file.separator")); + return System.getProperty("user.dir") + filepath.replace("\\", FileSystems.getDefault().getSeparator()); } @Test diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java index 2dbc7d4482a8..24817ae6ac9f 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java @@ -4,8 +4,8 @@ package com.azure.security.keyvault.jca.implementation.signature; import com.azure.security.keyvault.jca.KeyVaultEncode; -import com.azure.security.keyvault.jca.implementation.KeyVaultPrivateKey; import com.azure.security.keyvault.jca.implementation.KeyVaultClient; +import com.azure.security.keyvault.jca.implementation.KeyVaultPrivateKey; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -14,7 +14,8 @@ import java.security.PrivateKey; import java.security.PublicKey; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; 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..e18044f29d10 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 @@ -8,7 +8,9 @@ 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.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class HttpUtilTest { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtilTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtilTest.java index 8c3639174e81..02fcb0ec516f 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtilTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtilTest.java @@ -52,7 +52,7 @@ public void testToJson() { @Test void testFromJsonWithTokenResponseBody() { - AccessToken accessToken = null; + AccessToken accessToken; try { accessToken = JsonConverterUtil.fromJson(AccessToken::fromJson, DUMMY_TOKEN_RESPONSE_BODY); } catch (IOException e) { From 607ca6f723a2e4ee6013fb54216ce627a70e0d37 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:07:06 -0500 Subject: [PATCH 02/16] Finalize work --- .../azure-security-keyvault-jca/README.md | 108 +++++------ .../azure-security-keyvault-jca/pom.xml | 7 + .../KeyVaultKeylessEcSha256Signature.java | 2 +- .../KeyVaultKeylessEcSha384Signature.java | 2 +- .../KeyVaultKeylessEcSha512Signature.java | 2 +- .../KeyVaultKeylessRsa256Signature.java | 2 +- .../KeyVaultKeylessRsa512Signature.java | 2 +- .../KeyVaultKeylessRsaSsaPssSignature.java | 2 +- .../jca/implementation/utils/HttpUtil.java | 2 +- .../security/keyvault/jca/SampleUtils.java | 98 ++++++++++ .../jca/TrustSelfSignedServerDelegate.java | 50 ++++++ .../keyvault/jca/mtls/ClientMTLSSample.java | 66 ++++--- .../keyvault/jca/mtls/ServerMTLSSample.java | 5 +- .../keyvault/jca/tls/ClientSSLSample.java | 63 +++---- .../keyvault/jca/tls/ServerSSLSample.java | 5 +- .../security/keyvault/jca/JcaTestUtils.java | 168 ++++++++++++++++++ .../keyvault/jca/JreKeyStoreTest.java | 49 ++--- .../keyvault/jca/KeyVaultEncodeTest.java | 50 +++--- .../jca/KeyVaultKeyStoreUnitTest.java | 6 +- .../keyvault/jca/ServerSocketTest.java | 80 +++------ .../jca/SpecificPathCertificatesTest.java | 3 +- .../ClasspathCertificatesTest.java | 9 +- .../KeyVaultCertificatesTest.java | 58 ++++-- .../mocking/MockCertificate.java | 48 +++++ .../jca/implementation/mocking/MockKey.java | 22 +++ .../mocking/MockKeyVaultClient.java | 11 ++ .../mocking/MockPrivateKey.java | 22 +++ .../implementation/mocking/MockPublicKey.java | 22 +++ .../KeyVaultKeylessEcSignatureTest.java | 70 +++----- ...KeyVaultKeylessRsaSsaPssSignatureTest.java | 64 ++----- .../utils/JsonConverterUtilTest.java | 20 +-- 31 files changed, 739 insertions(+), 379 deletions(-) create mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/SampleUtils.java create mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/TrustSelfSignedServerDelegate.java create mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JcaTestUtils.java create mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockCertificate.java create mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockKey.java create mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockKeyVaultClient.java create mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockPrivateKey.java create mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockPublicKey.java diff --git a/sdk/keyvault/azure-security-keyvault-jca/README.md b/sdk/keyvault/azure-security-keyvault-jca/README.md index 90dbff7311f1..c34e16fbcb71 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/README.md +++ b/sdk/keyvault/azure-security-keyvault-jca/README.md @@ -131,8 +131,8 @@ while (true) { BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String body = "Hello, this is server."; - String response = - "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: " + body.getBytes("UTF-8").length + "\r\n" + "Connection: close\r\n" + "\r\n" + body; + String response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: " + + body.getBytes(StandardCharsets.UTF_8).length + "\r\nConnection: close\r\n\r\n" + body; out.write(response); out.flush(); @@ -156,34 +156,35 @@ Security.addProvider(provider); KeyStore keyStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); -SSLContext sslContext = SSLContexts - .custom() - .loadTrustMaterial(keyStore, new TrustSelfSignedStrategy()) - .build(); - -SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( - sslContext, (hostname, session) -> true); - -PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager( - RegistryBuilder.create() - .register("https", sslConnectionSocketFactory) - .build()); +// This section initializing SSLContext can be replaced with implementation specific consumption of 'KeyStore', +// if the library being used has convenience methods for that. +SSLContext sslContext = SSLContext.getInstance("TLS"); +TrustManager[] trustManagers = SampleUtils.loadTrustMaterial(keyStore); +sslContext.init(null, trustManagers, null); String result = null; - -try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).build()) { - HttpGet httpGet = new HttpGet("https://localhost:8765"); - HttpClientResponseHandler responseHandler = (ClassicHttpResponse response) -> { - int status = response.getCode(); - String result1 = "Not success"; - if (status == 200) { - result1 = EntityUtils.toString(response.getEntity()); - } - return result1; - }; - result = client.execute(httpGet, responseHandler); +HttpsURLConnection connection = null; +try { + // openConnection will return HttpsURLConnection when the protocol is 'https'. + connection = (HttpsURLConnection) URI.create("https://localhost:8765").toURL().openConnection(); + + // Have the HttpsURLConnection use the SSLSocketFactory returned by SSLContext. + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + + connection.setRequestMethod("GET"); + int status = connection.getResponseCode(); + if (status == 200) { + result = SampleUtils.readResponse(connection); + } else { + result = "Not success"; + } } catch (IOException ioe) { ioe.printStackTrace(); + result = "Not success"; +} finally { + if (connection != null) { + connection.disconnect(); + } } System.out.println(result); ``` @@ -229,8 +230,8 @@ while (true) { BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String body = "Hello, this is server."; - String response = - "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: " + body.getBytes("UTF-8").length + "\r\n" + "Connection: close\r\n" + "\r\n" + body; + String response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: " + + body.getBytes(StandardCharsets.UTF_8).length + "\r\nConnection: close\r\n\r\n" + body; out.write(response); out.flush(); @@ -259,35 +260,36 @@ System.setProperty("azure.keyvault.client-id", ""); KeyStore trustStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); -SSLContext sslContext = SSLContexts - .custom() - .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) - .loadKeyMaterial(keyStore, "".toCharArray()) - .build(); - -SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( - sslContext, (hostname, session) -> true); - -PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager( - RegistryBuilder.create() - .register("https", sslConnectionSocketFactory) - .build()); +// This section initializing SSLContext can be replaced with implementation specific consumption of 'KeyStore', +// if the library being used has convenience methods for that. +SSLContext sslContext = SSLContext.getInstance("TLS"); +TrustManager[] trustManagers = SampleUtils.loadTrustMaterial(keyStore); +KeyManager[] keyManagers = SampleUtils.loadKeyMaterial(keyStore, "".toCharArray()); +sslContext.init(keyManagers, trustManagers, null); String result = null; - -try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).build()) { - HttpGet httpGet = new HttpGet("https://localhost:8765"); - HttpClientResponseHandler responseHandler = (ClassicHttpResponse response) -> { - int status = response.getCode(); - String result1 = "Not success"; - if (status == 200) { - result1 = EntityUtils.toString(response.getEntity()); - } - return result1; - }; - result = client.execute(httpGet, responseHandler); +HttpsURLConnection connection = null; +try { + // openConnection will return HttpsURLConnection when the protocol is 'https'. + connection = (HttpsURLConnection) URI.create("https://localhost:8765").toURL().openConnection(); + + // Have the HttpsURLConnection use the SSLSocketFactory returned by SSLContext. + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + + connection.setRequestMethod("GET"); + int status = connection.getResponseCode(); + if (status == 200) { + result = SampleUtils.readResponse(connection); + } else { + result = "Not success"; + } } catch (IOException ioe) { ioe.printStackTrace(); + result = "Not success"; +} finally { + if (connection != null) { + connection.disconnect(); + } } System.out.println(result); ``` diff --git a/sdk/keyvault/azure-security-keyvault-jca/pom.xml b/sdk/keyvault/azure-security-keyvault-jca/pom.xml index d480be761abe..078b5409ca46 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-jca/pom.xml @@ -76,6 +76,13 @@ 1.17.7 test + + + com.github.spotbugs + spotbugs-annotations + 4.8.3 + test + org.junit.jupiter diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha256Signature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha256Signature.java index be8bf32deea7..564a66586518 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha256Signature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha256Signature.java @@ -9,7 +9,7 @@ public final class KeyVaultKeylessEcSha256Signature extends KeyVaultKeylessEcSig /** * Algorithm name used by this implementation. */ - public static String ALGORITHM_NAME = "SHA256withECDSA"; + public static final String ALGORITHM_NAME = "SHA256withECDSA"; @Override public String getAlgorithmName() { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha384Signature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha384Signature.java index 9e54533b6dac..81eea1c06d32 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha384Signature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha384Signature.java @@ -9,7 +9,7 @@ public final class KeyVaultKeylessEcSha384Signature extends KeyVaultKeylessEcSig /** * Algorithm name used by this implementation. */ - public static String ALGORITHM_NAME = "SHA384withECDSA"; + public static final String ALGORITHM_NAME = "SHA384withECDSA"; @Override public String getAlgorithmName() { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha512Signature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha512Signature.java index a468501c1e18..4accc59c0b15 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha512Signature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSha512Signature.java @@ -9,7 +9,7 @@ public final class KeyVaultKeylessEcSha512Signature extends KeyVaultKeylessEcSig /** * Algorithm name used by this implementation. */ - public static String ALGORITHM_NAME = "SHA512withECDSA"; + public static final String ALGORITHM_NAME = "SHA512withECDSA"; @Override public String getAlgorithmName() { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa256Signature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa256Signature.java index d73051353419..525be4c4f64a 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa256Signature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa256Signature.java @@ -10,7 +10,7 @@ public class KeyVaultKeylessRsa256Signature extends KeyVaultKeylessRsaSignature /** * Algorithm name used by this implementation. */ - public static String ALGORITHM_NAME = "SHA256withRSA"; + public static final String ALGORITHM_NAME = "SHA256withRSA"; @Override public String getAlgorithmName() { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa512Signature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa512Signature.java index 4d153405190a..d145aa323cb0 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa512Signature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsa512Signature.java @@ -10,7 +10,7 @@ public class KeyVaultKeylessRsa512Signature extends KeyVaultKeylessRsaSignature /** * Algorithm name used by this implementation. */ - public static String ALGORITHM_NAME = "SHA512withRSA"; + public static final String ALGORITHM_NAME = "SHA512withRSA"; @Override public String getAlgorithmName() { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignature.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignature.java index 0cd86b6bce05..0cff98ef0d35 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignature.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignature.java @@ -16,7 +16,7 @@ public class KeyVaultKeylessRsaSsaPssSignature extends KeyVaultKeylessRsaSignatu /** * Algorithm name used by this implementation. */ - public static String ALGORITHM_NAME = "RSASSA-PSS"; + public static final String ALGORITHM_NAME = "RSASSA-PSS"; @Override public String getAlgorithmName() { 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 8bd2682d2ece..64ff35f07e24 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 @@ -128,7 +128,7 @@ private static String createErrorMessage(int status) { @SuppressWarnings("StringOperationCanBeSimplified") private static String readResponseBody(HttpURLConnection connection) throws IOException { InputStream responseBody - = (connection.getInputStream() != null) ? connection.getInputStream() : connection.getErrorStream(); + = (connection.getErrorStream() != null) ? connection.getErrorStream() : connection.getInputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/SampleUtils.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/SampleUtils.java new file mode 100644 index 000000000000..f280959df1ec --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/SampleUtils.java @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.security.keyvault.jca; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +/** + * Utility methods for samples. + */ +public final class SampleUtils { + /** + * Loads the {@link TrustManager TrustManagers} for the {@link KeyStore}. + *

+ * This wraps {@link X509TrustManager X509TrustManagers} with {@link TrustSelfSignedServerDelegate} to support + * self-signed certificates. + * + * @param keyStore The {@link KeyStore} where {@link TrustManager TrustManagers} will be loaded. + * @return The {@link TrustManager TrustManagers} that were loaded. + * @throws NoSuchAlgorithmException If the algorithm used when calling + * {@link TrustManagerFactory#getInstance(String)} isn't available. + * @throws KeyStoreException If calling {@link TrustManagerFactory#init(KeyStore)} fails. + */ + public static TrustManager[] loadTrustMaterial(KeyStore keyStore) throws NoSuchAlgorithmException, + KeyStoreException { + TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmFactory.init(keyStore); + TrustManager[] trustManagers = tmFactory.getTrustManagers(); + if (trustManagers != null) { + for (int i = 0; i < trustManagers.length; i++) { + TrustManager trustManager = trustManagers[i]; + if (trustManager instanceof X509TrustManager) { + // Wrap X509TrustManagers with an implementation that trusts self-signed certificates. + // This doesn't need to be done and is just an example. + trustManagers[i] = new TrustSelfSignedServerDelegate((X509TrustManager) trustManager); + } + } + } + + return trustManagers; + } + + /** + * Loads the {@link KeyManager KeyManagers} for the {@link KeyStore}. + * + * @param keyStore The {@link KeyStore} where {@link KeyManager KeyManagers} will be loaded. + * @param password The password for recovering {@link KeyManager KeyManagers} in the {@link KeyStore}. + * @return The {@link KeyManager KeyManagers} that were loaded. + * @throws NoSuchAlgorithmException If the algorithm used when calling {@link KeyManagerFactory#getInstance(String)} + * isn't available. + * @throws KeyStoreException If calling {@link KeyManagerFactory#init(KeyStore, char[])} fails. + * @throws UnrecoverableKeyException If the {@link KeyManager} can't be recovered when calling + * {@link KeyManagerFactory#init(KeyStore, char[])}, such as the {@code password is wrong}. + */ + public static KeyManager[] loadKeyMaterial(KeyStore keyStore, char[] password) + throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { + KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmFactory.init(keyStore, password); + return kmFactory.getKeyManagers(); + } + + /** + * Reads the {@link HttpURLConnection} response body to a string. + * + * @param connection The {@link HttpURLConnection} to read the response body for. + * @return The response body as a string. + * @throws IOException If an I/O error occurs while reading the response body. + */ + @SuppressWarnings("StringOperationCanBeSimplified") + public static String readResponse(HttpURLConnection connection) throws IOException { + InputStream response = (connection.getInputStream() != null) + ? connection.getInputStream() + : connection.getErrorStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int read; + while ((read = response.read(buffer)) != -1) { + outputStream.write(buffer, 0, read); + } + + return new String(outputStream.toByteArray(), StandardCharsets.UTF_8); + } + + private SampleUtils() { + } +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/TrustSelfSignedServerDelegate.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/TrustSelfSignedServerDelegate.java new file mode 100644 index 000000000000..0103ce37b5e3 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/TrustSelfSignedServerDelegate.java @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.security.keyvault.jca; + +import javax.net.ssl.X509TrustManager; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Objects; + +/** + * Implementation of {@link X509TrustManager} that wraps another {@link X509TrustManager} with a check where self-signed + * server chains are trusted. + *

+ * This implementation uses basic validation for checking if the chain is self-signed, where it only checks that the + * chain has a length of one. This validation only applies when running + * {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)}, and if it passes that method call does not + * delegate to the wrapped {@link X509TrustManager}. + *

+ * {@link X509TrustManager#checkClientTrusted(X509Certificate[], String)} and + * {@link X509TrustManager#getAcceptedIssuers()} always delegate to the wrapped {@link X509TrustManager}. + */ +public final class TrustSelfSignedServerDelegate implements X509TrustManager { + private final X509TrustManager delegate; + + /** + * Creates a new instance of {@link TrustSelfSignedServerDelegate}. + * + * @param delegate The {@link X509TrustManager} that this {@link TrustSelfSignedServerDelegate} will delegate. + */ + public TrustSelfSignedServerDelegate(X509TrustManager delegate) { + this.delegate = Objects.requireNonNull(delegate, "'delegate' cannot be null."); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + delegate.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + if (chain.length != 1) { + checkServerTrusted(chain, authType); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return delegate.getAcceptedIssuers(); + } +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ClientMTLSSample.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ClientMTLSSample.java index c4884dbd2ed0..b3f3711b4a6f 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ClientMTLSSample.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ClientMTLSSample.java @@ -4,21 +4,14 @@ import com.azure.security.keyvault.jca.KeyVaultJcaProvider; import com.azure.security.keyvault.jca.KeyVaultKeyStore; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.client5.http.socket.ConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.config.RegistryBuilder; -import org.apache.hc.core5.http.io.HttpClientResponseHandler; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.ssl.SSLContexts; +import com.azure.security.keyvault.jca.SampleUtils; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; import java.io.IOException; +import java.net.URI; import java.security.KeyStore; import java.security.Security; @@ -44,35 +37,36 @@ public static void main(String[] args) throws Exception { System.setProperty("azure.keyvault.client-secret", ""); KeyStore trustStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); - SSLContext sslContext = SSLContexts - .custom() - .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) - .loadKeyMaterial(keyStore, "".toCharArray()) - .build(); - - SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( - sslContext, (hostname, session) -> true); - - PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager( - RegistryBuilder.create() - .register("https", sslConnectionSocketFactory) - .build()); + // This section initializing SSLContext can be replaced with implementation specific consumption of 'KeyStore', + // if the library being used has convenience methods for that. + SSLContext sslContext = SSLContext.getInstance("TLS"); + TrustManager[] trustManagers = SampleUtils.loadTrustMaterial(keyStore); + KeyManager[] keyManagers = SampleUtils.loadKeyMaterial(keyStore, "".toCharArray()); + sslContext.init(keyManagers, trustManagers, null); String result = null; + HttpsURLConnection connection = null; + try { + // openConnection will return HttpsURLConnection when the protocol is 'https'. + connection = (HttpsURLConnection) URI.create("https://localhost:8765").toURL().openConnection(); + + // Have the HttpsURLConnection use the SSLSocketFactory returned by SSLContext. + connection.setSSLSocketFactory(sslContext.getSocketFactory()); - try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).build()) { - HttpGet httpGet = new HttpGet("https://localhost:8765"); - HttpClientResponseHandler responseHandler = (ClassicHttpResponse response) -> { - int status = response.getCode(); - String result1 = "Not success"; - if (status == 200) { - result1 = EntityUtils.toString(response.getEntity()); - } - return result1; - }; - result = client.execute(httpGet, responseHandler); + connection.setRequestMethod("GET"); + int status = connection.getResponseCode(); + if (status == 200) { + result = SampleUtils.readResponse(connection); + } else { + result = "Not success"; + } } catch (IOException ioe) { ioe.printStackTrace(); + result = "Not success"; + } finally { + if (connection != null) { + connection.disconnect(); + } } System.out.println(result); // END: readme-sample-clientMTLS diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ServerMTLSSample.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ServerMTLSSample.java index 119272fa6edd..3f83b02ed56d 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ServerMTLSSample.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ServerMTLSSample.java @@ -13,6 +13,7 @@ import javax.net.ssl.TrustManagerFactory; import java.io.BufferedWriter; import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.Security; @@ -57,8 +58,8 @@ public static void main(String[] args) throws Exception { BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String body = "Hello, this is server."; - String response = - "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: " + body.getBytes("UTF-8").length + "\r\n" + "Connection: close\r\n" + "\r\n" + body; + String response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: " + + body.getBytes(StandardCharsets.UTF_8).length + "\r\nConnection: close\r\n\r\n" + body; out.write(response); out.flush(); diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ClientSSLSample.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ClientSSLSample.java index 5e0d4205ce2a..cf7d49a6f202 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ClientSSLSample.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ClientSSLSample.java @@ -4,21 +4,13 @@ import com.azure.security.keyvault.jca.KeyVaultJcaProvider; import com.azure.security.keyvault.jca.KeyVaultKeyStore; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.client5.http.socket.ConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.config.RegistryBuilder; -import org.apache.hc.core5.http.io.HttpClientResponseHandler; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.ssl.SSLContexts; +import com.azure.security.keyvault.jca.SampleUtils; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; import java.io.IOException; +import java.net.URI; import java.security.KeyStore; import java.security.Security; @@ -39,34 +31,35 @@ public static void main(String[] args) throws Exception { KeyStore keyStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); - SSLContext sslContext = SSLContexts - .custom() - .loadTrustMaterial(keyStore, new TrustSelfSignedStrategy()) - .build(); - - SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( - sslContext, (hostname, session) -> true); - - PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager( - RegistryBuilder.create() - .register("https", sslConnectionSocketFactory) - .build()); + // This section initializing SSLContext can be replaced with implementation specific consumption of 'KeyStore', + // if the library being used has convenience methods for that. + SSLContext sslContext = SSLContext.getInstance("TLS"); + TrustManager[] trustManagers = SampleUtils.loadTrustMaterial(keyStore); + sslContext.init(null, trustManagers, null); String result = null; + HttpsURLConnection connection = null; + try { + // openConnection will return HttpsURLConnection when the protocol is 'https'. + connection = (HttpsURLConnection) URI.create("https://localhost:8765").toURL().openConnection(); + + // Have the HttpsURLConnection use the SSLSocketFactory returned by SSLContext. + connection.setSSLSocketFactory(sslContext.getSocketFactory()); - try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).build()) { - HttpGet httpGet = new HttpGet("https://localhost:8765"); - HttpClientResponseHandler responseHandler = (ClassicHttpResponse response) -> { - int status = response.getCode(); - String result1 = "Not success"; - if (status == 200) { - result1 = EntityUtils.toString(response.getEntity()); - } - return result1; - }; - result = client.execute(httpGet, responseHandler); + connection.setRequestMethod("GET"); + int status = connection.getResponseCode(); + if (status == 200) { + result = SampleUtils.readResponse(connection); + } else { + result = "Not success"; + } } catch (IOException ioe) { ioe.printStackTrace(); + result = "Not success"; + } finally { + if (connection != null) { + connection.disconnect(); + } } System.out.println(result); // END: readme-sample-clientSSL diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ServerSSLSample.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ServerSSLSample.java index f925dd04a75c..491c8113c4ae 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ServerSSLSample.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ServerSSLSample.java @@ -12,6 +12,7 @@ import javax.net.ssl.SSLSocket; import java.io.BufferedWriter; import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.Security; @@ -47,8 +48,8 @@ public static void main(String[] args) throws Exception { BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String body = "Hello, this is server."; - String response = - "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: " + body.getBytes("UTF-8").length + "\r\n" + "Connection: close\r\n" + "\r\n" + body; + String response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: " + + body.getBytes(StandardCharsets.UTF_8).length + "\r\nConnection: close\r\n\r\n" + body; out.write(response); out.flush(); diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JcaTestUtils.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JcaTestUtils.java new file mode 100644 index 000000000000..0014f1b83915 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JcaTestUtils.java @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.security.keyvault.jca; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509TrustManager; +import java.net.Socket; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; + +/** + * Utility methods for testing KeyVault JCA. + */ +public final class JcaTestUtils { + /** + * Loads {@link TrustManager TrustManagers}. + * + * @param keyStore The {@link KeyStore}. + * @param trustStrategy An optional predicate that is used to skip calling + * {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)}. + * @return The {@link TrustManager TrustManagers}. + * @throws NoSuchAlgorithmException If the algorithm used when calling + * {@link TrustManagerFactory#getInstance(String)} doesn't exist. + * @throws KeyStoreException If {@link TrustManagerFactory#init(KeyStore)} fails. + */ + public static TrustManager[] loadTrustMaterial(KeyStore keyStore, + BiPredicate trustStrategy) throws NoSuchAlgorithmException, KeyStoreException { + TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmFactory.init(keyStore); + TrustManager[] trustManagers = tmFactory.getTrustManagers(); + + if (trustManagers != null && trustStrategy != null) { + for (int i = 0; i < trustManagers.length; i++) { + TrustManager trustManager = trustManagers[i]; + if (trustManager instanceof X509TrustManager) { + trustManagers[i] = new TrustManagerDelegate((X509TrustManager) trustManager, trustStrategy); + } + } + } + + return trustManagers; + } + + /** + * Loads {@link KeyManager KeyManagers}. + * + * @param keyStore The {@link KeyStore}. + * @param aliasStrategy An optional function to handle aliasing. + * @return The {@link KeyManager KeyManagers}. + * @throws NoSuchAlgorithmException If the algorithm used when calling {@link KeyManagerFactory#getInstance(String)} + * doesn't exist. + * @throws KeyStoreException If {@link KeyManagerFactory#init(KeyStore, char[])} fails. + * @throws UnrecoverableKeyException If the {@link KeyManager} can't be recovered when calling + * {@link KeyManagerFactory#init(KeyStore, char[])}, such as the {@code password is wrong}. + */ + public static KeyManager[] loadKeyMaterial(KeyStore keyStore, char[] password, + BiFunction aliasStrategy) + throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { + KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmFactory.init(keyStore, password); + KeyManager[] keyManagers = kmFactory.getKeyManagers(); + + if (keyManagers != null && aliasStrategy != null) { + for (int i = 0; i < keyManagers.length; i++) { + KeyManager keyManager = keyManagers[i]; + if (keyManager instanceof X509ExtendedKeyManager) { + keyManagers[i] = new KeyManagerDelegate((X509ExtendedKeyManager) keyManager, aliasStrategy); + } + } + } + + return keyManagers; + } + + private static final class TrustManagerDelegate implements X509TrustManager { + private final X509TrustManager delegate; + private final BiPredicate trustStrategy; + + private TrustManagerDelegate(X509TrustManager delegate, BiPredicate trustStrategy) { + this.delegate = delegate; + this.trustStrategy = trustStrategy; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + delegate.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + if (!trustStrategy.test(chain, authType)) { + delegate.checkServerTrusted(chain, authType); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return delegate.getAcceptedIssuers(); + } + } + + private static final class KeyManagerDelegate extends X509ExtendedKeyManager { + private final X509ExtendedKeyManager delegate; + private final BiFunction aliasStrategy; + + private KeyManagerDelegate(X509ExtendedKeyManager delegate, + BiFunction aliasStrategy) { + this.delegate = delegate; + this.aliasStrategy = aliasStrategy; + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return delegate.getClientAliases(keyType, issuers); + } + + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { + return aliasStrategy.apply(keyType, issuers); + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return delegate.getServerAliases(keyType, issuers); + } + + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { + return aliasStrategy.apply(new String[] { keyType }, issuers); + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + return delegate.getCertificateChain(alias); + } + + @Override + public PrivateKey getPrivateKey(String alias) { + return delegate.getPrivateKey(alias); + } + + @Override + public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) { + return aliasStrategy.apply(keyType, issuers); + } + + @Override + public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { + return aliasStrategy.apply(new String[] { keyType }, issuers); + } + } + + private JcaTestUtils() { + } +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java index 07b74d695e98..fcca34eb1a77 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java @@ -4,29 +4,21 @@ package com.azure.security.keyvault.jca; import com.azure.security.keyvault.jca.implementation.certificates.JreCertificates; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.client5.http.socket.ConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.config.RegistryBuilder; -import org.apache.hc.core5.http.io.HttpClientResponseHandler; -import org.apache.hc.core5.ssl.SSLContexts; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import java.io.IOException; +import java.net.URI; import java.security.KeyStore; import java.security.cert.Certificate; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIfEnvironmentVariable(named = "AZURE_KEYVAULT_CERTIFICATE_NAME", matches = "myalias") public class JreKeyStoreTest { @@ -48,7 +40,7 @@ public void testJreKsEntries() { assertNotNull(jreCertificates); assertNotNull(jreCertificates.getAliases()); Map certs = jreCertificates.getCertificates(); - assertTrue(certs.size() > 0); + assertFalse(certs.isEmpty()); assertNotNull(jreCertificates.getCertificateKeys()); } @@ -64,33 +56,26 @@ public void testJreKsTrustPeer() throws Exception { * - Create SSL connection factory. * - Set hostname verifier to trust any hostname. */ - - SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(ks, null).build(); - - SSLConnectionSocketFactory sslConnectionSocketFactory - = new SSLConnectionSocketFactory(sslContext, (hostname, session) -> true); - - PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager( - RegistryBuilder.create().register("https", sslConnectionSocketFactory).build()); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, JcaTestUtils.loadTrustMaterial(ks, null), null); /* * And now execute the test. */ String result = null; - - try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).build()) { - HttpGet httpGet = new HttpGet("https://google.com:443"); - HttpClientResponseHandler responseHandler = (ClassicHttpResponse response) -> { - int status = response.getCode(); - String result1 = null; - if (status == 200) { - result1 = "Success"; - } - return result1; - }; - result = client.execute(httpGet, responseHandler); + HttpsURLConnection connection = null; + try { + connection = (HttpsURLConnection) URI.create("https://google.com:443").toURL().openConnection(); + connection.setRequestMethod("GET"); + if (connection.getResponseCode() == 200) { + result = "Success"; + } } catch (IOException ioe) { ioe.printStackTrace(); + } finally { + if (connection != null) { + connection.disconnect(); + } } /* diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultEncodeTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultEncodeTest.java index d6e783d68224..39943cdb33b5 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultEncodeTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultEncodeTest.java @@ -21,43 +21,43 @@ public void buildLengthBytesTest() { Random random = new Random(); int a = random.nextInt(1 << 7); byte[] result = KeyVaultEncode.buildLengthBytes(TEST_TAG, a); - Assertions.assertEquals(result.length, 2); - Assertions.assertEquals(result[0], TEST_TAG); - Assertions.assertEquals(result[1], (byte) a); + Assertions.assertEquals(2, result.length); + Assertions.assertEquals(TEST_TAG, result[0]); + Assertions.assertEquals((byte) a, result[1]); a = random.nextInt((1 << 8) - (1 << 7)) + (1 << 7); result = KeyVaultEncode.buildLengthBytes(TEST_TAG, a); - Assertions.assertEquals(result.length, 3); - Assertions.assertEquals(result[0], TEST_TAG); - Assertions.assertEquals(result[1], (byte) 0x081); - Assertions.assertEquals(result[2], (byte) a); + Assertions.assertEquals(3, result.length); + Assertions.assertEquals(TEST_TAG, result[0]); + Assertions.assertEquals((byte) 0x081, result[1]); + Assertions.assertEquals((byte) a, result[2]); a = random.nextInt((1 << 16) - (1 << 8)) + (1 << 8); result = KeyVaultEncode.buildLengthBytes(TEST_TAG, a); - Assertions.assertEquals(result.length, 4); - Assertions.assertEquals(result[0], TEST_TAG); - Assertions.assertEquals(result[1], (byte) 0x082); - Assertions.assertEquals(result[2], (byte) (a >> 8)); - Assertions.assertEquals(result[3], (byte) a); + Assertions.assertEquals(4, result.length); + Assertions.assertEquals(TEST_TAG, result[0]); + Assertions.assertEquals((byte) 0x082, result[1]); + Assertions.assertEquals((byte) (a >> 8), result[2]); + Assertions.assertEquals((byte) a, result[3]); a = random.nextInt((1 << 24) - (1 << 16)) + (1 << 16); result = KeyVaultEncode.buildLengthBytes(TEST_TAG, a); - Assertions.assertEquals(result.length, 5); - Assertions.assertEquals(result[0], TEST_TAG); - Assertions.assertEquals(result[1], (byte) 0x083); - Assertions.assertEquals(result[2], (byte) (a >> 16)); - Assertions.assertEquals(result[3], (byte) (a >> 8)); - Assertions.assertEquals(result[4], (byte) a); + Assertions.assertEquals(5, result.length); + Assertions.assertEquals(TEST_TAG, result[0]); + Assertions.assertEquals((byte) 0x083, result[1]); + Assertions.assertEquals((byte) (a >> 16), result[2]); + Assertions.assertEquals((byte) (a >> 8), result[3]); + Assertions.assertEquals((byte) a, result[4]); a = random.nextInt((1 << 30) - (1 << 24)) + (1 << 24); result = KeyVaultEncode.buildLengthBytes(TEST_TAG, a); - Assertions.assertEquals(result.length, 6); - Assertions.assertEquals(result[0], TEST_TAG); - Assertions.assertEquals(result[1], (byte) 0x084); - Assertions.assertEquals(result[2], (byte) (a >> 24)); - Assertions.assertEquals(result[3], (byte) (a >> 16)); - Assertions.assertEquals(result[4], (byte) (a >> 8)); - Assertions.assertEquals(result[5], (byte) a); + Assertions.assertEquals(6, result.length); + Assertions.assertEquals(TEST_TAG, result[0]); + Assertions.assertEquals((byte) 0x084, result[1]); + Assertions.assertEquals((byte) (a >> 24), result[2]); + Assertions.assertEquals((byte) (a >> 16), result[3]); + Assertions.assertEquals((byte) (a >> 8), result[4]); + Assertions.assertEquals((byte) a, result[5]); } @Test diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyStoreUnitTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyStoreUnitTest.java index 9e290eaf45fc..af65bb94e014 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyStoreUnitTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultKeyStoreUnitTest.java @@ -51,12 +51,12 @@ public void testGetRefreshInterval() { System.clearProperty("azure.keyvault.jca.certificates-refresh-interval"); System.clearProperty("azure.keyvault.jca.certificates-refresh-interval-in-ms"); KeyVaultKeyStore keystore = new KeyVaultKeyStore(); - assertEquals(keystore.getRefreshInterval(), 0); + assertEquals(0, keystore.getRefreshInterval()); System.setProperty("azure.keyvault.jca.certificates-refresh-interval", "2000"); keystore = new KeyVaultKeyStore(); - assertEquals(keystore.getRefreshInterval(), 2000); + assertEquals(2000, keystore.getRefreshInterval()); System.setProperty("azure.keyvault.jca.certificates-refresh-interval-in-ms", "1000"); - assertEquals(keystore.getRefreshInterval(), 1000); + assertEquals(1000, keystore.getRefreshInterval()); } @Test diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java index bd2d91b7f6ad..a83d91e3cb87 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java @@ -3,36 +3,22 @@ package com.azure.security.keyvault.jca; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.client5.http.socket.ConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.config.RegistryBuilder; -import org.apache.hc.core5.http.io.HttpClientResponseHandler; -import org.apache.hc.core5.ssl.PrivateKeyDetails; -import org.apache.hc.core5.ssl.PrivateKeyStrategy; -import org.apache.hc.core5.ssl.SSLContexts; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.TrustManagerFactory; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; +import java.net.URI; import java.security.KeyStore; import java.security.Security; -import java.security.cert.X509Certificate; -import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -57,7 +43,7 @@ public static void beforeEach() throws Exception { KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); Security.addProvider(provider); - /** + /* * - Create an Azure Key Vault specific instance of a KeyStore. * - Set the KeyManagerFactory to use that KeyStore. */ @@ -86,16 +72,16 @@ private void startSocket(SSLServerSocket serverSocket) { @Test public void testHttpsConnectionWithoutClientTrust() throws Exception { - SSLContext sslContext = SSLContexts.custom() - .loadTrustMaterial((final X509Certificate[] chain, final String authType) -> true) - .build(); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, JcaTestUtils.loadTrustMaterial(null, (ignoredChain, ignoredAuthType) -> true), null); testHttpsConnection(8765, sslContext); } @Test public void testHttpsConnectionWithSelfSignedClientTrust() throws Exception { - SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(ks, new TrustSelfSignedStrategy()).build(); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, JcaTestUtils.loadTrustMaterial(ks, (chain, ignored) -> chain.length == 1), null); testHttpsConnection(8766, sslContext); } @@ -144,7 +130,7 @@ private void testHttpsConnection(Integer port, SSLContext sslContext) throws Exc assertEquals("Success", result); } - private void serverSocketWithTrustManager(Integer port) throws Exception { + private void serverSocketWithTrustManager(int port) throws Exception { /* * Setup server side. * @@ -169,11 +155,10 @@ private void serverSocketWithTrustManager(Integer port) throws Exception { * - Create an SSL context. * - Set SSL context to trust any certificate. */ - - SSLContext sslContext = SSLContexts.custom() - .loadTrustMaterial(ks, new TrustSelfSignedStrategy()) - .loadKeyMaterial(ks, "".toCharArray(), new ClientPrivateKeyStrategy()) - .build(); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init( + JcaTestUtils.loadKeyMaterial(ks, "".toCharArray(), (ignoredKeyTypes, ignoredIssuers) -> certificateName), + JcaTestUtils.loadTrustMaterial(ks, (chain, ignored) -> chain.length == 1), null); /* * And now execute the test. @@ -186,41 +171,28 @@ private void serverSocketWithTrustManager(Integer port) throws Exception { assertEquals("Success", result); } - private String sendRequest(SSLContext sslContext, Integer port) { + private String sendRequest(SSLContext sslContext, int port) { - /** + /* * - Create SSL connection factory. * - Set hostname verifier to trust any hostname. */ - SSLConnectionSocketFactory sslConnectionSocketFactory - = new SSLConnectionSocketFactory(sslContext, (hostname, session) -> true); - - PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager( - RegistryBuilder.create().register("https", sslConnectionSocketFactory).build()); - String result = null; - - try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).build()) { - HttpGet httpGet = new HttpGet("https://localhost:" + port); - HttpClientResponseHandler responseHandler = (ClassicHttpResponse response) -> { - int status = response.getCode(); - String result1 = null; - if (status == 204) { - result1 = "Success"; - } - return result1; - }; - result = client.execute(httpGet, responseHandler); + HttpsURLConnection connection = null; + try { + connection = (HttpsURLConnection) URI.create("https://localhost:" + port).toURL().openConnection(); + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + connection.setRequestMethod("GET"); + if (connection.getResponseCode() == 204) { + result = "Success"; + } } catch (IOException ioe) { ioe.printStackTrace(); + } finally { + if (connection != null) { + connection.disconnect(); + } } return result; } - - private static class ClientPrivateKeyStrategy implements PrivateKeyStrategy { - @Override - public String chooseAlias(Map aliases, SSLParameters sslParameters) { - return certificateName; // It should be your certificate alias used in client-side - } - } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/SpecificPathCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/SpecificPathCertificatesTest.java index 8932a202a584..dd4e6d5c71a8 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/SpecificPathCertificatesTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/SpecificPathCertificatesTest.java @@ -13,6 +13,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.FileSystems; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -35,7 +36,7 @@ public static void setEnvironmentProperty() { public static String getFilePath(String packageName) { String filepath = "\\src\\test\\resources\\" + packageName; - return System.getProperty("user.dir") + filepath.replace("\\", System.getProperty("file.separator")); + return System.getProperty("user.dir") + filepath.replace("\\", FileSystems.getDefault().getSeparator()); } @Test diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/ClasspathCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/ClasspathCertificatesTest.java index e2dab5e2f7b1..fed6265ab347 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/ClasspathCertificatesTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/ClasspathCertificatesTest.java @@ -3,15 +3,14 @@ package com.azure.security.keyvault.jca.implementation.certificates; -import static org.mockito.Mockito.mock; - -import java.security.cert.Certificate; +import com.azure.security.keyvault.jca.implementation.mocking.MockCertificate; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class ClasspathCertificatesTest { +import java.security.cert.Certificate; - private final Certificate certificate = mock(Certificate.class); +public class ClasspathCertificatesTest { + private final Certificate certificate = new MockCertificate(); @Test public void testSetCertificateEntry() { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java index e4dc803d6cc2..7e41fa8a92b9 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java @@ -3,35 +3,56 @@ package com.azure.security.keyvault.jca.implementation.certificates; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import com.azure.security.keyvault.jca.implementation.KeyVaultClient; +import com.azure.security.keyvault.jca.implementation.mocking.MockCertificate; +import com.azure.security.keyvault.jca.implementation.mocking.MockKey; +import com.azure.security.keyvault.jca.implementation.mocking.MockKeyVaultClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import java.security.Key; import java.security.cert.Certificate; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; public class KeyVaultCertificatesTest { + private final Key key = new MockKey(); + private final Certificate certificate = new MockCertificate(); - private final KeyVaultClient keyVaultClient = mock(KeyVaultClient.class); - - private final Key key = mock(Key.class); - - private final Certificate certificate = mock(Certificate.class); - + // This needs to be mutable. + private List aliases = new ArrayList<>(Collections.singletonList("myalias")); private KeyVaultCertificates keyVaultCertificates; @BeforeEach public void beforeEach() { - List aliases = new ArrayList<>(); - aliases.add("myalias"); - when(keyVaultClient.getAliases()).thenReturn(aliases); - when(keyVaultClient.getKey("myalias", null)).thenReturn(key); - when(keyVaultClient.getCertificate("myalias")).thenReturn(certificate); + KeyVaultClient keyVaultClient = new MockKeyVaultClient() { + @Override + public List getAliases() { + return aliases; + } + + @Override + public Key getKey(String alias, char[] password) { + return "myalias".equals(alias) && password == null ? key : null; + } + + @Override + public Certificate getCertificate(String alias) { + return "myalias".equals(alias) ? certificate : null; + } + + @Override + public Certificate[] getCertificateChain(String alias) { + return null; + } + + @Override + public byte[] getSignedWithPrivateKey(String digestName, String digestValue, String keyId) { + return null; + } + }; keyVaultCertificates = new KeyVaultCertificates(60_000, keyVaultClient); } @@ -54,7 +75,8 @@ public void testGetCertificate() { public void testRefreshAndGetAliasByCertificate() { Assertions.assertEquals("myalias", keyVaultCertificates.refreshAndGetAliasByCertificate(certificate)); Assertions.assertEquals(keyVaultCertificates.getCertificates().get("myalias"), certificate); - when(keyVaultClient.getAliases()).thenReturn(null); + + this.aliases = null; Assertions.assertNotEquals("myalias", keyVaultCertificates.refreshAndGetAliasByCertificate(certificate)); Assertions.assertNull(keyVaultCertificates.getCertificates().get("myalias")); } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockCertificate.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockCertificate.java new file mode 100644 index 000000000000..b4afd69c7ea9 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockCertificate.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.security.keyvault.jca.implementation.mocking; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; + +/** + * Mock of {@link Certificate}. + */ +public class MockCertificate extends Certificate { + public MockCertificate() { + super("mock"); + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return new byte[0]; + } + + @Override + public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, + NoSuchProviderException, SignatureException { + + } + + @Override + public void verify(PublicKey key, String sigProvider) throws CertificateException, NoSuchAlgorithmException, + InvalidKeyException, NoSuchProviderException, SignatureException { + + } + + @Override + public String toString() { + return ""; + } + + @Override + public PublicKey getPublicKey() { + return null; + } +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockKey.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockKey.java new file mode 100644 index 000000000000..c6021f17dc0f --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockKey.java @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.security.keyvault.jca.implementation.mocking; + +import java.security.Key; + +public class MockKey implements Key { + @Override + public String getAlgorithm() { + return ""; + } + + @Override + public String getFormat() { + return ""; + } + + @Override + public byte[] getEncoded() { + return new byte[0]; + } +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockKeyVaultClient.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockKeyVaultClient.java new file mode 100644 index 000000000000..94834e2f2ed2 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockKeyVaultClient.java @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.security.keyvault.jca.implementation.mocking; + +import com.azure.security.keyvault.jca.implementation.KeyVaultClient; + +public class MockKeyVaultClient extends KeyVaultClient { + public MockKeyVaultClient() { + super("https://accountname.vault.azure.net", "tenant-id", "client-id", "client-secret"); + } +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockPrivateKey.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockPrivateKey.java new file mode 100644 index 000000000000..b0fbd78f676b --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockPrivateKey.java @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.security.keyvault.jca.implementation.mocking; + +import java.security.PrivateKey; + +public class MockPrivateKey implements PrivateKey { + @Override + public String getAlgorithm() { + return null; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return new byte[0]; + } +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockPublicKey.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockPublicKey.java new file mode 100644 index 000000000000..4ee99bb9511e --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockPublicKey.java @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.security.keyvault.jca.implementation.mocking; + +import java.security.PublicKey; + +public class MockPublicKey implements PublicKey { + @Override + public String getAlgorithm() { + return null; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return new byte[0]; + } +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java index 24817ae6ac9f..5f0685815cd9 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java @@ -6,30 +6,29 @@ import com.azure.security.keyvault.jca.KeyVaultEncode; import com.azure.security.keyvault.jca.implementation.KeyVaultClient; import com.azure.security.keyvault.jca.implementation.KeyVaultPrivateKey; +import com.azure.security.keyvault.jca.implementation.mocking.MockKeyVaultClient; +import com.azure.security.keyvault.jca.implementation.mocking.MockPrivateKey; +import com.azure.security.keyvault.jca.implementation.mocking.MockPublicKey; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; import java.security.PrivateKey; import java.security.PublicKey; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class KeyVaultKeylessEcSignatureTest { KeyVaultKeylessEcSignature keyVaultKeylessEcSignature; - private final KeyVaultClient keyVaultClient = mock(KeyVaultClient.class); - - private final KeyVaultPrivateKey keyVaultPrivateKey = mock(KeyVaultPrivateKey.class); + private KeyVaultClient keyVaultClient; private final byte[] signedWithES256 = "fake256Value".getBytes(); private final byte[] signedWithES384 = "fake384Value".getBytes(); + private final PublicKey publicKey = new MockPublicKey(); + private final PrivateKey privateKey = new MockPrivateKey(); static final String KEY_VAULT_TEST_URI_GLOBAL = "https://fake.vault.azure.net/"; @@ -39,40 +38,6 @@ public void before() { keyVaultKeylessEcSignature = new KeyVaultKeylessEcSha256Signature(); } - private final PublicKey publicKey = new PublicKey() { - @Override - public String getAlgorithm() { - return null; - } - - @Override - public String getFormat() { - return null; - } - - @Override - public byte[] getEncoded() { - return new byte[0]; - } - }; - - private final PrivateKey privateKey = new PrivateKey() { - @Override - public String getAlgorithm() { - return null; - } - - @Override - public String getFormat() { - return null; - } - - @Override - public byte[] getEncoded() { - return new byte[0]; - } - }; - @Test public void engineInitVerifyTest() { assertThrows(UnsupportedOperationException.class, () -> keyVaultKeylessEcSignature.engineInitVerify(publicKey)); @@ -102,18 +67,31 @@ public void engineSetParameterTest() { @Test public void setDigestNameAndEngineSignTest() { + keyVaultClient = new MockKeyVaultClient() { + @Override + public byte[] getSignedWithPrivateKey(String digestName, String digestValue, String keyId) { + return "ES256".equals(digestName) ? signedWithES256 : null; + } + }; + KeyVaultPrivateKey keyVaultPrivateKey = new KeyVaultPrivateKey("algorithm", "kid") { + @Override + public KeyVaultClient getKeyVaultClient() { + return keyVaultClient; + } + }; keyVaultKeylessEcSignature = new KeyVaultKeylessEcSha256Signature(); - when(keyVaultClient.getSignedWithPrivateKey(ArgumentMatchers.eq("ES256"), anyString(), - ArgumentMatchers.eq(null))).thenReturn(signedWithES256); - when(keyVaultPrivateKey.getKeyVaultClient()).thenReturn(keyVaultClient); keyVaultKeylessEcSignature.engineInitSign(keyVaultPrivateKey, null); Assertions.assertArrayEquals(KeyVaultEncode.encodeByte(signedWithES256), keyVaultKeylessEcSignature.engineSign()); + keyVaultClient = new MockKeyVaultClient() { + @Override + public byte[] getSignedWithPrivateKey(String digestName, String digestValue, String keyId) { + return "ES384".equals(digestName) ? signedWithES384 : null; + } + }; keyVaultKeylessEcSignature = new KeyVaultKeylessEcSha384Signature(); keyVaultKeylessEcSignature.engineInitSign(keyVaultPrivateKey, null); - when(keyVaultClient.getSignedWithPrivateKey(ArgumentMatchers.eq("ES384"), anyString(), - ArgumentMatchers.eq(null))).thenReturn(signedWithES384); assertArrayEquals(KeyVaultEncode.encodeByte(signedWithES384), keyVaultKeylessEcSignature.engineSign()); } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignatureTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignatureTest.java index bda7ae92447b..946f3e35b843 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignatureTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignatureTest.java @@ -3,12 +3,15 @@ package com.azure.security.keyvault.jca.implementation.signature; -import com.azure.security.keyvault.jca.implementation.KeyVaultPrivateKey; import com.azure.security.keyvault.jca.implementation.KeyVaultClient; +import com.azure.security.keyvault.jca.implementation.KeyVaultPrivateKey; +import com.azure.security.keyvault.jca.implementation.mocking.MockKeyVaultClient; +import com.azure.security.keyvault.jca.implementation.mocking.MockPrivateKey; +import com.azure.security.keyvault.jca.implementation.mocking.MockPublicKey; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; +import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.PrivateKey; import java.security.PublicKey; @@ -18,19 +21,15 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class KeyVaultKeylessRsaSsaPssSignatureTest { KeyVaultKeylessRsaSsaPssSignature keyVaultKeylessRsaSsaPssSignature; - static final String KEY_VAULT_TEST_URI_GLOBAL = "https://fake.vault.azure.net/"; - - private final KeyVaultClient keyVaultClient = mock(KeyVaultClient.class); + private final PublicKey publicKey = new MockPublicKey(); + private final PrivateKey privateKey = new MockPrivateKey(); - private final KeyVaultPrivateKey keyVaultPrivateKey = mock(KeyVaultPrivateKey.class); + static final String KEY_VAULT_TEST_URI_GLOBAL = "https://fake.vault.azure.net/"; @BeforeEach public void before() { @@ -38,40 +37,6 @@ public void before() { keyVaultKeylessRsaSsaPssSignature = new KeyVaultKeylessRsaSsaPssSignature(); } - private final PublicKey publicKey = new PublicKey() { - @Override - public String getAlgorithm() { - return null; - } - - @Override - public String getFormat() { - return null; - } - - @Override - public byte[] getEncoded() { - return new byte[0]; - } - }; - - private final PrivateKey privateKey = new PrivateKey() { - @Override - public String getAlgorithm() { - return null; - } - - @Override - public String getFormat() { - return null; - } - - @Override - public byte[] getEncoded() { - return new byte[0]; - } - }; - @Test public void engineInitVerifyTest() { assertThrows(UnsupportedOperationException.class, @@ -103,13 +68,17 @@ public void engineSetParameterTest() { @Test public void setDigestNameAndEngineSignTest() throws InvalidAlgorithmParameterException { + KeyVaultClient keyVaultClient = new MockKeyVaultClient() { + @Override + public byte[] getSignedWithPrivateKey(String digestName, String digestValue, String keyId) { + return "PS256".equals(digestName) ? "fakeValue".getBytes(StandardCharsets.UTF_8) : null; + } + }; + KeyVaultPrivateKey keyVaultPrivateKey = new KeyVaultPrivateKey("algorithm", "kid", keyVaultClient); keyVaultKeylessRsaSsaPssSignature = new KeyVaultKeylessRsaSsaPssSignature(); - when(keyVaultPrivateKey.getKeyVaultClient()).thenReturn(keyVaultClient); keyVaultKeylessRsaSsaPssSignature.engineInitSign(keyVaultPrivateKey, null); keyVaultKeylessRsaSsaPssSignature .engineSetParameter(new PSSParameterSpec("SHA-1", "MGF1", MGF1ParameterSpec.SHA1, 20, 1)); - when(keyVaultClient.getSignedWithPrivateKey(ArgumentMatchers.eq("PS256"), anyString(), - ArgumentMatchers.eq(null))).thenReturn("fakeValue".getBytes()); assertArrayEquals("fakeValue".getBytes(), keyVaultKeylessRsaSsaPssSignature.engineSign()); } @@ -123,7 +92,8 @@ public void engineSetParameterWithNullParameterTest() { @Test public void engineSetParameterWithNotPSSParameterSpecTest() { keyVaultKeylessRsaSsaPssSignature = new KeyVaultKeylessRsaSsaPssSignature(); - AlgorithmParameterSpec algorithmParameterSpec = mock(AlgorithmParameterSpec.class); + AlgorithmParameterSpec algorithmParameterSpec = new AlgorithmParameterSpec() { + }; assertThrows(InvalidAlgorithmParameterException.class, () -> keyVaultKeylessRsaSsaPssSignature.engineSetParameter(algorithmParameterSpec)); } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtilTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtilTest.java index 02fcb0ec516f..23c13f00f9b1 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtilTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/JsonConverterUtilTest.java @@ -8,8 +8,7 @@ import com.azure.security.keyvault.jca.implementation.model.CertificateBundle; import org.junit.jupiter.api.Test; -import java.io.IOException; - +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -27,9 +26,9 @@ public class JsonConverterUtilTest { * Test the {@link JsonConverterUtil#fromJson(ReadValueCallback, String)} method. */ @Test - public void testFromJson() throws IOException { - String string = "{ \"cer\": \"cer\" }"; - CertificateBundle bundle = JsonConverterUtil.fromJson(CertificateBundle::fromJson, string); + public void testFromJson() { + CertificateBundle bundle + = assertDoesNotThrow(() -> JsonConverterUtil.fromJson(CertificateBundle::fromJson, "{\"cer\":\"cer\"}")); assertNotNull(bundle); assertEquals("cer", bundle.getCer()); @@ -41,23 +40,18 @@ public void testFromJson() throws IOException { @Test public void testToJson() { CertificateBundle bundle = new CertificateBundle(); - bundle.setCer("value"); String string = JsonConverterUtil.toJson(bundle); - assertTrue(string.contains("cer")); + assertTrue(string.contains("\"cer\"")); assertTrue(string.contains("\"value\"")); } @Test void testFromJsonWithTokenResponseBody() { - AccessToken accessToken; - try { - accessToken = JsonConverterUtil.fromJson(AccessToken::fromJson, DUMMY_TOKEN_RESPONSE_BODY); - } catch (IOException e) { - throw new RuntimeException(e); - } + AccessToken accessToken + = assertDoesNotThrow(() -> JsonConverterUtil.fromJson(AccessToken::fromJson, DUMMY_TOKEN_RESPONSE_BODY)); assertNotNull(accessToken); assertEquals("test_access_token_value", accessToken.getAccessToken()); } From 8c9c5602044b0c2ced7f7bc8ae734c179e45ec10 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:26:45 -0500 Subject: [PATCH 03/16] Finalize work --- .../jca/implementation/{mocking => }/MockKeyVaultClient.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/{mocking => }/MockKeyVaultClient.java (100%) diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockKeyVaultClient.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/MockKeyVaultClient.java similarity index 100% rename from sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/mocking/MockKeyVaultClient.java rename to sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/MockKeyVaultClient.java From 32fa6fd5fb05e59b8566ecb70ac3f53b72932450 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:45:41 -0500 Subject: [PATCH 04/16] Remove Mockito --- .../azure-security-keyvault-jca/pom.xml | 28 -- .../jca/implementation/KeyVaultClient.java | 33 +- .../keyvault/jca/KeyVaultEncodeTest.java | 18 +- .../implementation/KeyVaultClientTest.java | 351 +++++++++--------- .../implementation/MockKeyVaultClient.java | 4 +- .../KeyVaultCertificatesTest.java | 2 +- .../KeyVaultKeylessEcSignatureTest.java | 2 +- ...KeyVaultKeylessRsaSsaPssSignatureTest.java | 2 +- 8 files changed, 215 insertions(+), 225 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/pom.xml b/sdk/keyvault/azure-security-keyvault-jca/pom.xml index 078b5409ca46..f469d95f3476 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-jca/pom.xml @@ -55,34 +55,6 @@ slf4j-nop 1.7.36 - - - org.mockito - mockito-inline - 4.11.0 - test - - - - - net.bytebuddy - byte-buddy - 1.17.7 - test - - - net.bytebuddy - byte-buddy-agent - 1.17.7 - test - - - - com.github.spotbugs - spotbugs-annotations - 4.8.3 - test - org.junit.jupiter diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java index 725e3c999698..a1e98178d5d8 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java @@ -226,7 +226,7 @@ private AccessToken obtainAccessToken() { // Priority: 1. Managed Identity, 2. Provided Access Token, 3. Client ID/Secret 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 @@ -238,8 +238,7 @@ private AccessToken obtainAccessToken() { 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); } } catch (UnsupportedEncodingException e) { LOGGER.log(WARNING, "Could not obtain access token to authenticate with.", e); @@ -263,7 +262,7 @@ public List getAliases() { 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) { @@ -301,7 +300,7 @@ private CertificateBundle getCertificateBundle(String alias) { LOGGER.entering("KeyVaultClient", "getCertificateBundle", alias); CertificateBundle result = null; - String response = HttpUtil.get(keyVaultUri + "certificates/" + alias + API_VERSION_POSTFIX, + String response = httpGet(keyVaultUri + "certificates/" + alias + API_VERSION_POSTFIX, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (response != null) { @@ -362,7 +361,7 @@ public Certificate[] getCertificateChain(String alias) { LOGGER.log(INFO, "Getting certificate chain for alias: {0}", alias); String uri = keyVaultUri + "secrets/" + alias + API_VERSION_POSTFIX; - String response = HttpUtil.get(uri, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); + String response = httpGet(uri, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (response == null) { throw new NullPointerException(); @@ -431,7 +430,7 @@ public Key getKey(String alias, char[] password) { return key; } - String body = HttpUtil.get(certificateBundle.getSid() + API_VERSION_POSTFIX, + String body = httpGet(certificateBundle.getSid() + API_VERSION_POSTFIX, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (body == null) { @@ -504,8 +503,7 @@ public byte[] getSignedWithPrivateKey(String digestName, String digestValue, Str String bodyString = "{\"alg\": \"" + digestName + "\", \"value\": \"" + digestValue + "\"}"; Map 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("/sign" + API_VERSION_POSTFIX, headers, bodyString, "application/json"); if (response != null) { try { @@ -579,4 +577,21 @@ private PrivateKey createPrivateKeyFromPem(String pemString, String keyType) return privateKey; } + + String httpGet(String uri, Map headers) { + return HttpUtil.get(uri, headers); + } + + String httpPost(String uri, Map headers, String body, String contentType) { + return HttpUtil.post(uri, headers, body, contentType); + } + + AccessToken getAccessToken(String resource, String identity) { + return AccessTokenUtil.getAccessToken(resource, managedIdentity); + } + + AccessToken getAccessToken(String resource, String aadAuthenticationUri, String tenantId, String clientId, + String clientSecret) { + return AccessTokenUtil.getAccessToken(resource, aadAuthenticationUri, tenantId, clientId, clientSecret); + } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultEncodeTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultEncodeTest.java index 39943cdb33b5..2e77690995a8 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultEncodeTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/KeyVaultEncodeTest.java @@ -3,11 +3,11 @@ package com.azure.security.keyvault.jca; -import net.bytebuddy.utility.RandomString; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.math.BigInteger; +import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; import java.util.Random; @@ -62,9 +62,10 @@ public void buildLengthBytesTest() { @Test public void concatBytesWithThreeBytes() { - byte[] byte1 = RandomString.make(32).getBytes(); - byte[] byte2 = RandomString.make(32).getBytes(); - byte[] byte3 = RandomString.make(32).getBytes(); + SecureRandom random = new SecureRandom(); + byte[] byte1 = random.generateSeed(32); + byte[] byte2 = random.generateSeed(32); + byte[] byte3 = random.generateSeed(32); byte[] result = KeyVaultEncode.concatBytes(byte1, byte2, byte3); Assertions.assertArrayEquals(byte1, Arrays.copyOfRange(result, 0, byte1.length)); Assertions.assertArrayEquals(byte2, Arrays.copyOfRange(result, byte1.length, byte1.length + byte2.length)); @@ -73,8 +74,9 @@ public void concatBytesWithThreeBytes() { @Test public void concatBytesWithTwoBytes() { - byte[] byte1 = RandomString.make(32).getBytes(); - byte[] byte2 = RandomString.make(32).getBytes(); + SecureRandom random = new SecureRandom(); + byte[] byte1 = random.generateSeed(32); + byte[] byte2 = random.generateSeed(32); byte[] result = KeyVaultEncode.concatBytes(byte1, byte2); Assertions.assertArrayEquals(byte1, Arrays.copyOfRange(result, 0, byte1.length)); Assertions.assertArrayEquals(byte2, Arrays.copyOfRange(result, byte1.length, result.length)); @@ -82,8 +84,8 @@ public void concatBytesWithTwoBytes() { @Test public void toBigIntegerBytesWithLengthPrefixTest() { - byte[] testByte = RandomString.make(32).getBytes(); - Random random = new Random(); + SecureRandom random = new SecureRandom(); + byte[] testByte = random.generateSeed(32); int offset = random.nextInt(testByte.length); int length = random.nextInt(testByte.length - offset); byte[] result = KeyVaultEncode.toBigIntegerBytesWithLengthPrefix(testByte, offset, length); diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java index 22f80900efa6..348f7935192a 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java @@ -7,236 +7,239 @@ import com.azure.security.keyvault.jca.implementation.model.AccessToken; import com.azure.security.keyvault.jca.implementation.model.CertificateItem; import com.azure.security.keyvault.jca.implementation.model.CertificateListResult; -import com.azure.security.keyvault.jca.implementation.utils.AccessTokenUtil; -import com.azure.security.keyvault.jca.implementation.utils.HttpUtil; import com.azure.security.keyvault.jca.implementation.utils.JsonConverterUtil; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.mockito.MockedStatic; -import org.mockito.Mockito; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.notNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; public class KeyVaultClientTest { private static final String KEY_VAULT_TEST_URI_GLOBAL = "https://fake.vault.azure.net/"; @Test public void testGetAliasWithCertificateInfoWith0Page() { - try (MockedStatic utilities = Mockito.mockStatic(HttpUtil.class)) { - utilities.when(() -> HttpUtil.get(anyString(), anyMap())).thenReturn("fakeValue"); - - KeyVaultClient keyVaultClient = mock(KeyVaultClient.class); - List result = keyVaultClient.getAliases(); - - assertEquals(0, result.size()); - } + KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { + @Override + String httpGet(String uri, Map headers) { + return "fakeValue"; + } + }; + + assertEquals(0, keyVaultClient.getAliases().size()); } @Test public void testGetAliasWithCertificateInfoWith1Page() { - try (MockedStatic utilities = Mockito.mockStatic(HttpUtil.class)) { - utilities.when(() -> HttpUtil.validateUri(anyString(), anyString())).thenCallRealMethod(); - utilities.when(() -> HttpUtil.addTrailingSlashIfRequired(anyString())).thenCallRealMethod(); - - // Create fake certificates. - CertificateItem fakeCertificateItem1 = new CertificateItem(); - fakeCertificateItem1.setId("certificates/fakeCertificateItem1"); + // Create fake certificates. + CertificateItem fakeCertificateItem1 = new CertificateItem(); + fakeCertificateItem1.setId("certificates/fakeCertificateItem1"); - CertificateListResult certificateListResult = new CertificateListResult(); - certificateListResult.setValue(Arrays.asList(fakeCertificateItem1)); + CertificateListResult certificateListResult = new CertificateListResult(); + certificateListResult.setValue(Arrays.asList(fakeCertificateItem1)); - String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); + String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - utilities.when(() -> HttpUtil.get(notNull(), anyMap())).thenReturn(certificateListResultString); + KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { + @Override + String httpGet(String uri, Map headers) { + return certificateListResultString; + } + }; - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null); - List result = keyVaultClient.getAliases(); - - assertEquals(1, result.size()); - assertTrue(result.contains("fakeCertificateItem1")); - } + List result = keyVaultClient.getAliases(); + assertEquals(1, result.size()); + assertTrue(result.contains("fakeCertificateItem1")); } @Test public void testGetAliasWithCertificateInfoWith2Pages() { - try (MockedStatic utilities = Mockito.mockStatic(HttpUtil.class)) { - utilities.when(() -> HttpUtil.validateUri(anyString(), anyString())).thenCallRealMethod(); - utilities.when(() -> HttpUtil.addTrailingSlashIfRequired(anyString())).thenCallRealMethod(); - - // create fake certificates - CertificateItem fakeCertificateItem1 = new CertificateItem(); - fakeCertificateItem1.setId("certificates/fakeCertificateItem1"); - - CertificateItem fakeCertificateItem2 = new CertificateItem(); - fakeCertificateItem2.setId("certificates/fakeCertificateItem2"); - - CertificateItem fakeCertificateItem3 = new CertificateItem(); - fakeCertificateItem3.setId("certificates/fakeCertificateItem3"); - - // Create first page certificate result. - CertificateListResult certificateListResult = new CertificateListResult(); - certificateListResult.setNextLink("fakeNextLint"); - certificateListResult.setValue(Arrays.asList(fakeCertificateItem1)); - - // Create next page certificate result. - CertificateListResult certificateListResultNext = new CertificateListResult(); - certificateListResultNext.setValue(Arrays.asList(fakeCertificateItem2, fakeCertificateItem3)); - - String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - String certificateListResultStringNext = JsonConverterUtil.toJson(certificateListResultNext); - - utilities.when(() -> HttpUtil.get(notNull(), anyMap())).thenReturn(certificateListResultString); - utilities.when(() -> HttpUtil.get(eq("fakeNextLint"), anyMap())) - .thenReturn(certificateListResultStringNext); - - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null); - List result = keyVaultClient.getAliases(); - - assertEquals(3, result.size()); - assertTrue(result - .containsAll(Arrays.asList("fakeCertificateItem1", "fakeCertificateItem2", "fakeCertificateItem3"))); - } + // create fake certificates + CertificateItem fakeCertificateItem1 = new CertificateItem(); + fakeCertificateItem1.setId("certificates/fakeCertificateItem1"); + + CertificateItem fakeCertificateItem2 = new CertificateItem(); + fakeCertificateItem2.setId("certificates/fakeCertificateItem2"); + + CertificateItem fakeCertificateItem3 = new CertificateItem(); + fakeCertificateItem3.setId("certificates/fakeCertificateItem3"); + + // Create first page certificate result. + CertificateListResult certificateListResult = new CertificateListResult(); + certificateListResult.setNextLink("fakeNextLink"); + certificateListResult.setValue(Arrays.asList(fakeCertificateItem1)); + + // Create next page certificate result. + CertificateListResult certificateListResultNext = new CertificateListResult(); + certificateListResultNext.setValue(Arrays.asList(fakeCertificateItem2, fakeCertificateItem3)); + + String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); + String certificateListResultStringNext = JsonConverterUtil.toJson(certificateListResultNext); + + KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { + @Override + String httpGet(String uri, Map headers) { + return "fakeNextLink".equals(uri) ? certificateListResultStringNext : certificateListResultString; + } + }; + + List result = keyVaultClient.getAliases(); + assertEquals(3, result.size()); + assertTrue( + result.containsAll(Arrays.asList("fakeCertificateItem1", "fakeCertificateItem2", "fakeCertificateItem3"))); } @Test public void testCacheToken() { - try (MockedStatic tokenUtilMockedStatic = Mockito.mockStatic(AccessTokenUtil.class); - MockedStatic httpUtilMockedStatic = Mockito.mockStatic(HttpUtil.class)) { - - httpUtilMockedStatic.when(() -> HttpUtil.validateUri(anyString(), anyString())).thenCallRealMethod(); - httpUtilMockedStatic.when(() -> HttpUtil.addTrailingSlashIfRequired(anyString())).thenCallRealMethod(); - - AccessToken cacheToken = new AccessToken(); - cacheToken.setExpiresIn(300); // 300 seconds. - - tokenUtilMockedStatic.when(() -> AccessTokenUtil.getAccessToken(anyString(), anyString())) - .thenReturn(cacheToken); - - CertificateItem fakeCertificateItem = new CertificateItem(); - fakeCertificateItem.setId("certificates/fakeCertificateItem"); - - CertificateListResult certificateListResult = new CertificateListResult(); - certificateListResult.setValue(Arrays.asList(fakeCertificateItem)); - - String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - httpUtilMockedStatic.when(() -> HttpUtil.get(anyString(), anyMap())) - .thenReturn(certificateListResultString); - - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, ""); - keyVaultClient.getAliases(); - keyVaultClient.getAliases(); // Get aliases the second time. - - tokenUtilMockedStatic.verify(() -> AccessTokenUtil.getAccessToken(anyString(), anyString()), times(1)); - } + AccessToken cacheToken = new AccessToken(); + cacheToken.setExpiresIn(300); // 300 seconds. + + CertificateItem fakeCertificateItem = new CertificateItem(); + fakeCertificateItem.setId("certificates/fakeCertificateItem"); + + CertificateListResult certificateListResult = new CertificateListResult(); + certificateListResult.setValue(Arrays.asList(fakeCertificateItem)); + + String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); + + AtomicInteger getAccessTokenCount = new AtomicInteger(); + KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, "") { + @Override + String httpGet(String uri, Map headers) { + return certificateListResultString; + } + + @Override + AccessToken getAccessToken(String resource, String identity) { + getAccessTokenCount.incrementAndGet(); + return cacheToken; + } + }; + keyVaultClient.getAliases(); + keyVaultClient.getAliases(); // Get aliases the second time. + + assertEquals(1, getAccessTokenCount.get()); } @Test public void testCacheTokenExpired() { - try (MockedStatic tokenUtilMockedStatic = Mockito.mockStatic(AccessTokenUtil.class); - MockedStatic httpUtilMockedStatic = Mockito.mockStatic(HttpUtil.class)) { + AccessToken cacheToken = new AccessToken(); + cacheToken.setExpiresIn(50); // 50 seconds. - httpUtilMockedStatic.when(() -> HttpUtil.validateUri(anyString(), anyString())).thenCallRealMethod(); - httpUtilMockedStatic.when(() -> HttpUtil.addTrailingSlashIfRequired(anyString())).thenCallRealMethod(); + CertificateItem fakeCertificateItem = new CertificateItem(); + fakeCertificateItem.setId("certificates/fakeCertificateItem"); - AccessToken cacheToken = new AccessToken(); - cacheToken.setExpiresIn(50); // 50 seconds. + CertificateListResult certificateListResult = new CertificateListResult(); + certificateListResult.setValue(Arrays.asList(fakeCertificateItem)); - tokenUtilMockedStatic.when(() -> AccessTokenUtil.getAccessToken(anyString(), anyString())) - .thenReturn(cacheToken); + String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - CertificateItem fakeCertificateItem = new CertificateItem(); - fakeCertificateItem.setId("certificates/fakeCertificateItem"); + AtomicInteger getAccessTokenCount = new AtomicInteger(); + KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, "") { + @Override + String httpGet(String uri, Map headers) { + return certificateListResultString; + } - CertificateListResult certificateListResult = new CertificateListResult(); - certificateListResult.setValue(Arrays.asList(fakeCertificateItem)); + @Override + AccessToken getAccessToken(String resource, String identity) { + getAccessTokenCount.incrementAndGet(); + return cacheToken; + } + }; - String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - httpUtilMockedStatic.when(() -> HttpUtil.get(anyString(), anyMap())) - .thenReturn(certificateListResultString); + keyVaultClient.getAliases(); + keyVaultClient.getAliases(); // Get aliases the second time. - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, ""); - keyVaultClient.getAliases(); - keyVaultClient.getAliases(); // Get aliases the second time. - - tokenUtilMockedStatic.verify(() -> AccessTokenUtil.getAccessToken(anyString(), anyString()), times(2)); - } + assertEquals(2, getAccessTokenCount.get()); } @Test public void testAccessTokenAuthentication() { - try (MockedStatic httpUtilMockedStatic = Mockito.mockStatic(HttpUtil.class)) { - httpUtilMockedStatic.when(() -> HttpUtil.validateUri(anyString(), anyString())).thenCallRealMethod(); - httpUtilMockedStatic.when(() -> HttpUtil.addTrailingSlashIfRequired(anyString())).thenCallRealMethod(); + CertificateItem fakeCertificateItem = new CertificateItem(); + fakeCertificateItem.setId("certificates/fakeCertificateItem"); - CertificateItem fakeCertificateItem = new CertificateItem(); - fakeCertificateItem.setId("certificates/fakeCertificateItem"); + CertificateListResult certificateListResult = new CertificateListResult(); + certificateListResult.setValue(Arrays.asList(fakeCertificateItem)); - CertificateListResult certificateListResult = new CertificateListResult(); - certificateListResult.setValue(Arrays.asList(fakeCertificateItem)); + String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - httpUtilMockedStatic.when(() -> HttpUtil.get(anyString(), anyMap())) - .thenReturn(certificateListResultString); + // Create client with access token + String testAccessToken = "test-bearer-token-12345"; + KeyVaultClient keyVaultClient + = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null, null, null, null, testAccessToken, false) { + @Override + String httpGet(String uri, Map headers) { + return certificateListResultString; + } + }; - // Create client with access token - String testAccessToken = "test-bearer-token-12345"; - KeyVaultClient keyVaultClient - = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null, null, null, null, testAccessToken, false); + List result = keyVaultClient.getAliases(); - List result = keyVaultClient.getAliases(); - - // Verify that the access token was used - assertEquals(1, result.size()); - assertTrue(result.contains("fakeCertificateItem")); - } + // Verify that the access token was used + assertEquals(1, result.size()); + assertTrue(result.contains("fakeCertificateItem")); } @Test public void testAuthenticationPriority() { - try (MockedStatic httpUtilMockedStatic = Mockito.mockStatic(HttpUtil.class); - MockedStatic tokenUtilMockedStatic = Mockito.mockStatic(AccessTokenUtil.class)) { - - httpUtilMockedStatic.when(() -> HttpUtil.validateUri(anyString(), anyString())).thenCallRealMethod(); - httpUtilMockedStatic.when(() -> HttpUtil.addTrailingSlashIfRequired(anyString())).thenCallRealMethod(); - - AccessToken accessToken = new AccessToken("fake-token", 3600); - tokenUtilMockedStatic.when(() -> AccessTokenUtil.getAccessToken(anyString(), anyString())) - .thenReturn(accessToken); - - CertificateItem fakeCertificateItem = new CertificateItem(); - fakeCertificateItem.setId("certificates/fakeCertificateItem"); - - CertificateListResult certificateListResult = new CertificateListResult(); - certificateListResult.setValue(Arrays.asList(fakeCertificateItem)); - - String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - httpUtilMockedStatic.when(() -> HttpUtil.get(anyString(), anyMap())) - .thenReturn(certificateListResultString); - - // Test 1: Managed Identity should take priority over access token - KeyVaultClient client1 - = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null, null, null, "managed-id", "bearer-token", false); - client1.getAliases(); - tokenUtilMockedStatic.verify(() -> AccessTokenUtil.getAccessToken(anyString(), eq("managed-id")), times(1)); - - // Test 2: Access token should be used when managed identity is not set - KeyVaultClient client2 - = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null, null, null, null, "bearer-token", false); - List result = client2.getAliases(); - assertEquals(1, result.size()); - assertTrue(result.contains("fakeCertificateItem")); - } + AtomicInteger getAccessTokenCount = new AtomicInteger(); + AccessToken accessToken = new AccessToken("fake-token", 3600); + + CertificateItem fakeCertificateItem = new CertificateItem(); + fakeCertificateItem.setId("certificates/fakeCertificateItem"); + + CertificateListResult certificateListResult = new CertificateListResult(); + certificateListResult.setValue(Arrays.asList(fakeCertificateItem)); + + String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); + + // Test 1: Managed Identity should take priority over access token + KeyVaultClient client1 + = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null, null, null, "managed-id", "bearer-token", false) { + @Override + String httpGet(String uri, Map headers) { + return certificateListResultString; + } + + @Override + AccessToken getAccessToken(String resource, String identity) { + if ("managed-id".equals(identity)) { + getAccessTokenCount.incrementAndGet(); + } + return accessToken; + } + }; + client1.getAliases(); + + // Test 2: Access token should be used when managed identity is not set + KeyVaultClient client2 + = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null, null, null, null, "bearer-token", false) { + @Override + String httpGet(String uri, Map headers) { + return certificateListResultString; + } + + @Override + AccessToken getAccessToken(String resource, String identity) { + if ("managed-id".equals(identity)) { + getAccessTokenCount.incrementAndGet(); + } + return accessToken; + } + }; + + List result = client2.getAliases(); + assertEquals(1, result.size()); + assertTrue(result.contains("fakeCertificateItem")); + + assertEquals(1, getAccessTokenCount.get()); } @EnabledIfEnvironmentVariable(named = "AZURE_KEYVAULT_CERTIFICATE_NAME", matches = "myalias") diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/MockKeyVaultClient.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/MockKeyVaultClient.java index 94834e2f2ed2..5d3d656297e9 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/MockKeyVaultClient.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/MockKeyVaultClient.java @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.security.keyvault.jca.implementation.mocking; - -import com.azure.security.keyvault.jca.implementation.KeyVaultClient; +package com.azure.security.keyvault.jca.implementation; public class MockKeyVaultClient extends KeyVaultClient { public MockKeyVaultClient() { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java index 7e41fa8a92b9..3756c8646fe2 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java @@ -6,7 +6,7 @@ import com.azure.security.keyvault.jca.implementation.KeyVaultClient; import com.azure.security.keyvault.jca.implementation.mocking.MockCertificate; import com.azure.security.keyvault.jca.implementation.mocking.MockKey; -import com.azure.security.keyvault.jca.implementation.mocking.MockKeyVaultClient; +import com.azure.security.keyvault.jca.implementation.MockKeyVaultClient; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java index 5f0685815cd9..798dbb25d332 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessEcSignatureTest.java @@ -6,7 +6,7 @@ import com.azure.security.keyvault.jca.KeyVaultEncode; import com.azure.security.keyvault.jca.implementation.KeyVaultClient; import com.azure.security.keyvault.jca.implementation.KeyVaultPrivateKey; -import com.azure.security.keyvault.jca.implementation.mocking.MockKeyVaultClient; +import com.azure.security.keyvault.jca.implementation.MockKeyVaultClient; import com.azure.security.keyvault.jca.implementation.mocking.MockPrivateKey; import com.azure.security.keyvault.jca.implementation.mocking.MockPublicKey; import org.junit.jupiter.api.Assertions; diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignatureTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignatureTest.java index 946f3e35b843..08ad83fb48ac 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignatureTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/signature/KeyVaultKeylessRsaSsaPssSignatureTest.java @@ -5,7 +5,7 @@ import com.azure.security.keyvault.jca.implementation.KeyVaultClient; import com.azure.security.keyvault.jca.implementation.KeyVaultPrivateKey; -import com.azure.security.keyvault.jca.implementation.mocking.MockKeyVaultClient; +import com.azure.security.keyvault.jca.implementation.MockKeyVaultClient; import com.azure.security.keyvault.jca.implementation.mocking.MockPrivateKey; import com.azure.security.keyvault.jca.implementation.mocking.MockPublicKey; import org.junit.jupiter.api.BeforeEach; From 19ff957402ef16e9ed5f71c2ff1499643ce9cf58 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:30:44 -0400 Subject: [PATCH 05/16] Merge in main and resolve merge conflict --- .../eventhubs/stress/scenarios/EventForwarder.java | 3 +-- .../messaging/eventhubs/stress/util/TelemetryHelper.java | 2 +- sdk/keyvault/azure-security-keyvault-jca/pom.xml | 1 - .../keyvault/jca/implementation/KeyVaultClient.java | 6 +++--- .../keyvault/jca/implementation/utils/AccessTokenUtil.java | 3 +-- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java index 4fd67886541d..907ecf0f6873 100644 --- a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java +++ b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java @@ -105,8 +105,7 @@ public void run() { private EventHubProducerAsyncClient getForwardProducer() { final TokenCredential tokenCredential = new DefaultAzureCredentialBuilder().build(); final EventHubClientBuilder builder = new EventHubClientBuilder() - .credential(options.getEventHubsFullyQualifiedNamespace(), forwardEventHubName, - tokenCredential) + .credential(options.getEventHubsFullyQualifiedNamespace(), forwardEventHubName, tokenCredential) .retryOptions(new AmqpRetryOptions().setTryTimeout(Duration.ofSeconds(10))) .transportType(options.getAmqpTransportType()) .consumerGroup(options.getEventHubsConsumerGroup()); diff --git a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java index 393155d8914e..0f9f6a37a768 100644 --- a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java +++ b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java @@ -90,7 +90,7 @@ public TelemetryHelper(Class scenarioClass) { */ private static OpenTelemetry init() { System.setProperty("otel.java.global-autoconfigure.enabled", "true"); - + AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder(); String applicationInsightsConnectionString = System.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"); if (applicationInsightsConnectionString == null) { diff --git a/sdk/keyvault/azure-security-keyvault-jca/pom.xml b/sdk/keyvault/azure-security-keyvault-jca/pom.xml index b68124cc33ee..c970a22bd4ae 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-jca/pom.xml @@ -1,5 +1,4 @@ - diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java index fe1fe0d1106e..7a529b965917 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java @@ -512,7 +512,7 @@ public byte[] getSignedWithPrivateKey(String digestName, String digestValue, Str String bodyString = "{\"alg\": \"" + digestName + "\", \"value\": \"" + digestValue + "\"}"; Map headers = Collections.singletonMap("Authorization", "Bearer " + getAccessToken()); - String response = httpPost("/sign" + API_VERSION_POSTFIX, headers, bodyString, "application/json"); + String response = httpPost(headers, bodyString); if (response != null) { try { @@ -591,8 +591,8 @@ String httpGet(String uri, Map headers) { return HttpUtil.get(uri, headers); } - String httpPost(String uri, Map headers, String body, String contentType) { - return HttpUtil.post(uri, headers, body, contentType); + String httpPost(Map headers, String body) { + return HttpUtil.post("/sign?api-version=7.1", headers, body, "application/json"); } AccessToken getAccessToken(String resource, String identity) { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java index 2be57d9e457b..9534bff23dd2 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java @@ -188,7 +188,6 @@ public static boolean isWorkloadIdentityAvailable(String clientId, String tenant * @param resource The resource scope (will be appended with /.default if not already present). * @param tenantId Tenant ID to use. If blank, fallback to environment variable AZURE_TENANT_ID. * @param clientId Client ID of the managed identity to use. If blank, fallback to environment variable AZURE_CLIENT_ID. - * @param tokenFilePath Path to the federated token file. If blank, fallback to environment variable AZURE_FEDERATED_TOKEN_FILE. * @return An access token, or null if the operation fails. */ public static AccessToken getAccessTokenWithWorkloadIdentity(String resource, String tenantId, String clientId) { @@ -219,7 +218,7 @@ public static AccessToken getAccessTokenWithWorkloadIdentity(String resource, St String requestUrl = buildTokenRequestUrl(authorityHost, effectiveTenantId); String requestBody = buildTokenRequestBody(effectiveClientId, federatedToken, scope); - String response = HttpUtil.post(requestUrl, requestBody, "application/x-www-form-urlencoded"); + String response = HttpUtil.post(requestUrl, null, requestBody, "application/x-www-form-urlencoded"); AccessToken result = parseAccessTokenResponse(response); LOGGER.exiting("AccessTokenUtil", "getAccessTokenWithWorkloadIdentity", result); From 2feddfbe51e7d67523aa117f818c8150bebe4e99 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 7 Aug 2026 18:37:56 -0700 Subject: [PATCH 06/16] Removed Mockito and related dependencies from POM --- .../azure-security-keyvault-jca/pom.xml | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/pom.xml b/sdk/keyvault/azure-security-keyvault-jca/pom.xml index c970a22bd4ae..074c49f4ce83 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-jca/pom.xml @@ -55,34 +55,6 @@ 1.7.36 - - - org.mockito - mockito-inline - 4.11.0 - test - - - - - net.bytebuddy - byte-buddy - 1.17.7 - test - - - net.bytebuddy - byte-buddy-agent - 1.17.7 - test - - - - com.github.spotbugs - spotbugs-annotations - 4.8.3 - test - com.azure azure-core From 33caba1f72b700fb3fb4f16a81eb9a036ec388f2 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 7 Aug 2026 19:03:58 -0700 Subject: [PATCH 07/16] Fixed compilation issue --- .../implementation/KeyVaultClientTest.java | 144 +++++++++--------- 1 file changed, 70 insertions(+), 74 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java index b303f7b21632..0c6090159979 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java @@ -99,93 +99,89 @@ String httpGet(String uri, Map headers) { @Test public void testGetAliasFiltersOutDisabledCertificate() { - try (MockedStatic utilities = Mockito.mockStatic(HttpUtil.class)) { - utilities.when(() -> HttpUtil.validateUri(anyString(), anyString())).thenCallRealMethod(); - utilities.when(() -> HttpUtil.addTrailingSlashIfRequired(anyString())).thenCallRealMethod(); - - // Enabled certificate. - CertificateItemAttributes enabledAttributes = new CertificateItemAttributes(); - enabledAttributes.setEnabled(true); - CertificateItem enabledCertificate = new CertificateItem(); - enabledCertificate.setId("certificates/client-cert-active"); - enabledCertificate.setAttributes(enabledAttributes); - - // Disabled certificate. This one previously caused an HTTP 403 while initializing the keystore. - CertificateItemAttributes disabledAttributes = new CertificateItemAttributes(); - disabledAttributes.setEnabled(false); - CertificateItem disabledCertificate = new CertificateItem(); - disabledCertificate.setId("certificates/client-cert-unused"); - disabledCertificate.setAttributes(disabledAttributes); - - CertificateListResult certificateListResult = new CertificateListResult(); - certificateListResult.setValue(Arrays.asList(enabledCertificate, disabledCertificate)); - - String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - utilities.when(() -> HttpUtil.get(notNull(), anyMap())).thenReturn(certificateListResultString); - - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null); - List result = keyVaultClient.getAliases(); - - assertEquals(1, result.size()); - assertTrue(result.contains("client-cert-active")); - assertFalse(result.contains("client-cert-unused")); - } + // Enabled certificate. + CertificateItemAttributes enabledAttributes = new CertificateItemAttributes(); + enabledAttributes.setEnabled(true); + CertificateItem enabledCertificate = new CertificateItem(); + enabledCertificate.setId("certificates/client-cert-active"); + enabledCertificate.setAttributes(enabledAttributes); + + // Disabled certificate. This one previously caused an HTTP 403 while initializing the keystore. + CertificateItemAttributes disabledAttributes = new CertificateItemAttributes(); + disabledAttributes.setEnabled(false); + CertificateItem disabledCertificate = new CertificateItem(); + disabledCertificate.setId("certificates/client-cert-unused"); + disabledCertificate.setAttributes(disabledAttributes); + + CertificateListResult certificateListResult = new CertificateListResult(); + certificateListResult.setValue(Arrays.asList(enabledCertificate, disabledCertificate)); + + String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); + + KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { + @Override + String httpGet(String uri, Map headers) { + return certificateListResultString; + } + }; + List result = keyVaultClient.getAliases(); + + assertEquals(1, result.size()); + assertTrue(result.contains("client-cert-active")); + assertFalse(result.contains("client-cert-unused")); } @Test public void testGetAliasKeepsEnabledAndAttributelessCertificates() { - try (MockedStatic utilities = Mockito.mockStatic(HttpUtil.class)) { - utilities.when(() -> HttpUtil.validateUri(anyString(), anyString())).thenCallRealMethod(); - utilities.when(() -> HttpUtil.addTrailingSlashIfRequired(anyString())).thenCallRealMethod(); - - // Certificate explicitly enabled. - CertificateItemAttributes enabledAttributes = new CertificateItemAttributes(); - enabledAttributes.setEnabled(true); - CertificateItem enabledCertificate = new CertificateItem(); - enabledCertificate.setId("certificates/enabledCertificate"); - enabledCertificate.setAttributes(enabledAttributes); + // Certificate explicitly enabled. + CertificateItemAttributes enabledAttributes = new CertificateItemAttributes(); + enabledAttributes.setEnabled(true); + CertificateItem enabledCertificate = new CertificateItem(); + enabledCertificate.setId("certificates/enabledCertificate"); + enabledCertificate.setAttributes(enabledAttributes); - // Certificate without attributes, which must be treated as enabled for backward compatibility. - CertificateItem attributelessCertificate = new CertificateItem(); - attributelessCertificate.setId("certificates/attributelessCertificate"); + // Certificate without attributes, which must be treated as enabled for backward compatibility. + CertificateItem attributelessCertificate = new CertificateItem(); + attributelessCertificate.setId("certificates/attributelessCertificate"); - CertificateListResult certificateListResult = new CertificateListResult(); - certificateListResult.setValue(Arrays.asList(enabledCertificate, attributelessCertificate)); + CertificateListResult certificateListResult = new CertificateListResult(); + certificateListResult.setValue(Arrays.asList(enabledCertificate, attributelessCertificate)); - String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - utilities.when(() -> HttpUtil.get(notNull(), anyMap())).thenReturn(certificateListResultString); + String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null); - List result = keyVaultClient.getAliases(); + KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { + @Override + String httpGet(String uri, Map headers) { + return certificateListResultString; + } + }; + List result = keyVaultClient.getAliases(); - assertEquals(2, result.size()); - assertTrue(result.containsAll(Arrays.asList("enabledCertificate", "attributelessCertificate"))); - } + assertEquals(2, result.size()); + assertTrue(result.containsAll(Arrays.asList("enabledCertificate", "attributelessCertificate"))); } @Test public void testGetAliasFiltersDisabledCertificateFromRawResponse() { - try (MockedStatic utilities = Mockito.mockStatic(HttpUtil.class)) { - utilities.when(() -> HttpUtil.validateUri(anyString(), anyString())).thenCallRealMethod(); - utilities.when(() -> HttpUtil.addTrailingSlashIfRequired(anyString())).thenCallRealMethod(); - - // A response that mirrors the shape returned by the Azure Key Vault "list certificates" REST API, with one - // enabled and one disabled certificate. - String rawResponse = "{\"value\":[" - + "{\"id\":\"https://fake.vault.azure.net/certificates/client-cert-active\"," - + "\"attributes\":{\"enabled\":true,\"nbf\":1783324860,\"exp\":1814861460}}," - + "{\"id\":\"https://fake.vault.azure.net/certificates/client-cert-unused\"," - + "\"attributes\":{\"enabled\":false,\"nbf\":1783324860,\"exp\":1814861460}}]," + "\"nextLink\":null}"; - - utilities.when(() -> HttpUtil.get(notNull(), anyMap())).thenReturn(rawResponse); - - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null); - List result = keyVaultClient.getAliases(); - - assertEquals(1, result.size()); - assertTrue(result.contains("client-cert-active")); - assertFalse(result.contains("client-cert-unused")); - } + // A response that mirrors the shape returned by the Azure Key Vault "list certificates" REST API, with one + // enabled and one disabled certificate. + String rawResponse = "{\"value\":[" + + "{\"id\":\"https://fake.vault.azure.net/certificates/client-cert-active\"," + + "\"attributes\":{\"enabled\":true,\"nbf\":1783324860,\"exp\":1814861460}}," + + "{\"id\":\"https://fake.vault.azure.net/certificates/client-cert-unused\"," + + "\"attributes\":{\"enabled\":false,\"nbf\":1783324860,\"exp\":1814861460}}]," + "\"nextLink\":null}"; + + KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { + @Override + String httpGet(String uri, Map headers) { + return rawResponse; + } + }; + List result = keyVaultClient.getAliases(); + + assertEquals(1, result.size()); + assertTrue(result.contains("client-cert-active")); + assertFalse(result.contains("client-cert-unused")); } @Test From 7bf40cea8bd748322b37e4437ecccc1257b0a413 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Mon, 10 Aug 2026 09:44:28 -0700 Subject: [PATCH 08/16] Ran Spotless --- .../jca/implementation/KeyVaultClientTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java index 0c6090159979..18d9662c3ded 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java @@ -165,11 +165,11 @@ String httpGet(String uri, Map headers) { public void testGetAliasFiltersDisabledCertificateFromRawResponse() { // A response that mirrors the shape returned by the Azure Key Vault "list certificates" REST API, with one // enabled and one disabled certificate. - String rawResponse = "{\"value\":[" - + "{\"id\":\"https://fake.vault.azure.net/certificates/client-cert-active\"," - + "\"attributes\":{\"enabled\":true,\"nbf\":1783324860,\"exp\":1814861460}}," - + "{\"id\":\"https://fake.vault.azure.net/certificates/client-cert-unused\"," - + "\"attributes\":{\"enabled\":false,\"nbf\":1783324860,\"exp\":1814861460}}]," + "\"nextLink\":null}"; + String rawResponse + = "{\"value\":[" + "{\"id\":\"https://fake.vault.azure.net/certificates/client-cert-active\"," + + "\"attributes\":{\"enabled\":true,\"nbf\":1783324860,\"exp\":1814861460}}," + + "{\"id\":\"https://fake.vault.azure.net/certificates/client-cert-unused\"," + + "\"attributes\":{\"enabled\":false,\"nbf\":1783324860,\"exp\":1814861460}}]," + "\"nextLink\":null}"; KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { @Override From 46f542df3932fa2f80e895414ebd57b9795be100 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Thu, 27 Aug 2026 01:17:13 -0700 Subject: [PATCH 09/16] More changes after merge --- .../stress/scenarios/EventForwarder.java | 3 +- .../stress/util/TelemetryHelper.java | 2 +- .../jca/implementation/KeyVaultClient.java | 34 +-- .../implementation/utils/AccessTokenUtil.java | 2 +- .../utils/AiaCertificateChainUtil.java | 23 +- .../jca/implementation/utils/HttpUtil.java | 241 ++++++++++++------ .../KeyVaultCertificatesTest.java | 12 + .../implementation/utils/HttpUtilTest.java | 66 ++--- 8 files changed, 244 insertions(+), 139 deletions(-) diff --git a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java index 907ecf0f6873..4fd67886541d 100644 --- a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java +++ b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java @@ -105,7 +105,8 @@ public void run() { private EventHubProducerAsyncClient getForwardProducer() { final TokenCredential tokenCredential = new DefaultAzureCredentialBuilder().build(); final EventHubClientBuilder builder = new EventHubClientBuilder() - .credential(options.getEventHubsFullyQualifiedNamespace(), forwardEventHubName, tokenCredential) + .credential(options.getEventHubsFullyQualifiedNamespace(), forwardEventHubName, + tokenCredential) .retryOptions(new AmqpRetryOptions().setTryTimeout(Duration.ofSeconds(10))) .transportType(options.getAmqpTransportType()) .consumerGroup(options.getEventHubsConsumerGroup()); diff --git a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java index 0f9f6a37a768..393155d8914e 100644 --- a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java +++ b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java @@ -90,7 +90,7 @@ public TelemetryHelper(Class scenarioClass) { */ private static OpenTelemetry init() { System.setProperty("otel.java.global-autoconfigure.enabled", "true"); - + AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder(); String applicationInsightsConnectionString = System.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"); if (applicationInsightsConnectionString == null) { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java index a8b7de1d63d2..86117827d975 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java @@ -264,7 +264,7 @@ private AccessToken obtainAccessToken() { result = AccessTokenUtil.getAccessTokenWithWorkloadIdentity(keyVaultBaseUri, tenantId, clientId); } else if (managedIdentity != null) { LOGGER.info("Using managed identity for authentication"); - result = getAccessToken(resource, managedIdentity); + result = AccessTokenUtil.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 @@ -274,7 +274,7 @@ private AccessToken obtainAccessToken() { result = new AccessToken(providedAccessToken, Long.MAX_VALUE / 1000); } else { LOGGER.info("Using managed identity for authentication (default)"); - result = getAccessToken(resource, null); + result = AccessTokenUtil.getAccessToken(resource, null); } } catch (UnsupportedEncodingException e) { LOGGER.log(WARNING, "Could not obtain access token to authenticate with.", e); @@ -298,7 +298,7 @@ public List getAliases() { String uri = keyVaultUri + "certificates" + API_VERSION_POSTFIX; while (uri != null && !uri.isEmpty()) { - String response = httpGet(uri, headers); + String response = HttpUtil.get(uri, headers); CertificateListResult certificateListResult = null; if (response != null) { @@ -344,7 +344,7 @@ private CertificateBundle getCertificateBundle(String alias) { LOGGER.entering("KeyVaultClient", "getCertificateBundle", alias); CertificateBundle result = null; - String response = httpGet(keyVaultUri + "certificates/" + alias + API_VERSION_POSTFIX, + String response = HttpUtil.get(keyVaultUri + "certificates/" + alias + API_VERSION_POSTFIX, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (response != null) { @@ -460,7 +460,7 @@ public Certificate[] getCertificateChainForVersion(CertificateVersion certificat return new Certificate[0]; } - String response = httpGet(certificateVersion.getSecretId() + API_VERSION_POSTFIX, + String response = HttpUtil.get(certificateVersion.getSecretId() + API_VERSION_POSTFIX, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (response == null) { @@ -540,7 +540,8 @@ public Key getKeyForVersion(CertificateVersion certificateVersion, char[] passwo if (certificateSecretUri == null) { return null; } - String body = httpGet(certificateSecretUri + API_VERSION_POSTFIX, + + String body = HttpUtil.get(certificateSecretUri + API_VERSION_POSTFIX, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (body == null) { @@ -612,8 +613,8 @@ public byte[] getSignedWithPrivateKey(String digestName, String digestValue, Str SignResult result = null; String bodyString = "{\"alg\": \"" + digestName + "\", \"value\": \"" + digestValue + "\"}"; Map headers = Collections.singletonMap("Authorization", "Bearer " + getAccessToken()); - - String response = httpPost(keyId + "/sign" + API_VERSION_POSTFIX, headers, bodyString); + String uri = keyId + "/sign" + API_VERSION_POSTFIX; + String response = HttpUtil.post(uri, headers, bodyString, "application/json"); if (response != null) { try { @@ -688,21 +689,4 @@ private PrivateKey createPrivateKeyFromPem(String pemString, String keyType) return privateKey; } - - String httpGet(String uri, Map headers) { - return HttpUtil.get(uri, headers); - } - - String httpPost(String uri, Map headers, String body) { - return HttpUtil.post(uri, headers, body, "application/json"); - } - - AccessToken getAccessToken(String resource, String identity) { - return AccessTokenUtil.getAccessToken(resource, managedIdentity); - } - - AccessToken getAccessToken(String resource, String aadAuthenticationUri, String tenantId, String clientId, - String clientSecret) { - return AccessTokenUtil.getAccessToken(resource, aadAuthenticationUri, tenantId, clientId, clientSecret); - } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java index e30024fced46..94a029acdd94 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java @@ -439,7 +439,7 @@ public static String getLoginUri(String resourceUri, boolean disableChallengeRes LOGGER.entering("AccessTokenUtil", "getLoginUri", resourceUri); LOGGER.log(INFO, "Getting login URI using: {0}", resourceUri); - Map> headers = HttpUtil.getWithResponseHeadersOnlyReturn(resourceUri); + Map> headers = HttpUtil.getWithOnlyResponseHeaders(resourceUri); if (headers == null) { throw new IllegalStateException("Could not obtain login URI to retrieve access token from."); diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java index af7dd126a6a4..17c3688a5765 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java @@ -55,10 +55,12 @@ final class AiaCertificateChainUtil { private static final int AIA_CACHE_MAX_SIZE = 128; private static final long MAX_SUCCESS_TTL_IN_MILLIS = TimeUnit.HOURS.toMillis(24); private static final long NEGATIVE_TTL_IN_MILLIS = TimeUnit.MINUTES.toMillis(1); - private static final AiaResponseCache AIA_CACHE - = new AiaResponseCache(AIA_CACHE_MAX_SIZE, System::currentTimeMillis, (message, parameters) -> LOGGER.logp(FINE, + private static final AiaResponseCache AIA_CACHE = + new AiaResponseCache(AIA_CACHE_MAX_SIZE, System::currentTimeMillis, (message, parameters) -> LOGGER.logp(FINE, AiaResponseCache.class.getName(), "diagnostic", message, parameters)); - private static final AiaResponseLoader DEFAULT_RESPONSE_LOADER = HttpUtil::getBytesWithMetadata; + // A default HTTP-based response loader for AIA requests. + private static final AiaResponseLoader DEFAULT_RESPONSE_LOADER = HttpUtil::getAiaBytesWithMetadata; + // The currently configured response loader, which can be overridden for tests. private static volatile AiaResponseLoader responseLoader = DEFAULT_RESPONSE_LOADER; /** @@ -597,14 +599,29 @@ static void clearAiaCache() { AIA_CACHE.clear(); } + /** + * Sets the response loader for AIA requests. + * + *

This can be used to override the default HTTP-based loader, for example in tests. + * + * @param loader the response loader to use + */ static synchronized void setResponseLoader(AiaResponseLoader loader) { responseLoader = Objects.requireNonNull(loader, "'loader' cannot be null."); } + /** + * Resets the response loader for AIA requests to the default HTTP-based loader. + * + *

This can be used to undo any overrides set by {@link #setResponseLoader(AiaResponseLoader)}. + */ static synchronized void resetResponseLoader() { responseLoader = DEFAULT_RESPONSE_LOADER; } + /** + * Functional interface for loading AIA responses. + */ @FunctionalInterface interface AiaResponseLoader { HttpUtil.BinaryHttpResponse load(String url); 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 9188dd90686a..4014fe07cec7 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 @@ -50,30 +50,51 @@ public final class HttpUtil { static final String DEFAULT_USER_AGENT_VALUE_PREFIX = "az-se-kv-jca/"; private static final Logger LOGGER = Logger.getLogger(HttpUtil.class.getName()); + private static final int AIA_HTTP_TIMEOUT_IN_MILLISECONDS = 10_000; static final int MAX_AIA_RESPONSE_SIZE_IN_BYTES = 10 * 1024 * 1024; private static final int AIA_HTTP_TOTAL_TIMEOUT_IN_MILLISECONDS = 30_000; private static final int MAX_AIA_REDIRECTS = 5; + @FunctionalInterface + interface ConnectionFactory { + HttpURLConnection open(String url) throws IOException; + } + + /** + * Performs an HTTP GET request to the specified URI with the given headers. + * + * @param uri the URI to send the GET request to + * @param headers the headers to include in the request + * @return the response body as a string, or {@code null} if the request fails + */ public static String get(String uri, Map headers) { return get(uri, headers, HttpUtil::openConnection); } + // Overloaded method that allows specifying a custom ConnectionFactory for testing purposes. static String get(String uri, Map headers, ConnectionFactory connectionFactory) { HttpURLConnection connection = null; + try { connection = connectionFactory.open(uri); + connection.setRequestMethod("GET"); if (headers != null) { headers.forEach(connection::setRequestProperty); } + connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); - ensureSuccessfulResponse(connection.getResponseCode()); - return readResponseBody(connection); + if (isSuccessfulResponse(connection.getResponseCode())) { + return readResponseBody(connection); + } + + return null; } catch (IOException ioe) { LOGGER.log(WARNING, "Unable to finish the HTTP GET request.", ioe); + return null; } finally { if (connection != null) { @@ -83,33 +104,92 @@ static String get(String uri, Map headers, ConnectionFactory con } /** - * Performs an HTTP GET request and returns the raw response body as a byte array. - * Used primarily for downloading DER-encoded certificates from CA Issuers URLs in - * AIA (Authority Information Access) certificate extensions. + * Performs an HTTP POST request to the specified URI with the given headers, body, and content type. * - * @param url the URL to fetch - * @return the response body bytes, or {@code null} if the request fails or returns non-2xx + * @param uri the URI to send the POST request to + * @param headers the headers to include in the request + * @param body the body of the POST request + * @param contentType the content type of the POST request body + * @return the response body as a string, or {@code null} if the request fails */ - public static byte[] getBytes(String url) { - return getBytesWithMetadata(url).getBody(); + public static String post(String uri, Map headers, String body, String contentType) { + return post(uri, headers, body, contentType, HttpUtil::openConnection); } - static BinaryHttpResponse getBytesWithMetadata(String url) { - return getBytesWithMetadata(url, HttpUtil::openConnection); + // Overloaded method that allows specifying a custom ConnectionFactory for testing purposes. + static String post(String uri, Map headers, String body, String contentType, + ConnectionFactory connectionFactory) { + + HttpURLConnection connection = null; + + try { + connection = connectionFactory.open(uri); + + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + + if (headers != null) { + headers.forEach(connection::setRequestProperty); + } + + if (contentType != null) { + connection.setRequestProperty("Content-Type", contentType); + } + + connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); + + try (OutputStream outputStream = connection.getOutputStream()) { + outputStream.write(body.getBytes(StandardCharsets.UTF_8)); + } + + if (isSuccessfulResponse(connection.getResponseCode())) { + return readResponseBody(connection); + } + + return null; + } catch (IOException ioe) { + LOGGER.log(WARNING, "Unable to finish the HTTP POST request.", ioe); + + return null; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + /** + * Performs an HTTP GET request and returns the response body along with HTTP metadata. Used primarily for + * downloading DER-encoded certificates from CA Issuers URLs in AIA (Authority Information Access) certificate + * extensions. + * + * @param url the URL to fetch + * @return the response body bytes, or {@code null} if the request fails or returns non-2xx + */ + public static BinaryHttpResponse getAiaBytesWithMetadata(String url) { + return getAiaBytesWithMetadata(url, HttpUtil::openConnection); } - static BinaryHttpResponse getBytesWithMetadata(String url, ConnectionFactory connectionFactory) { + // Overloaded method that allows specifying a custom ConnectionFactory for testing purposes. + static BinaryHttpResponse getAiaBytesWithMetadata(String url, ConnectionFactory connectionFactory) { String currentUrl; + try { currentUrl = validateAiaUrl(url); } catch (IllegalArgumentException e) { LOGGER.log(WARNING, "Unable to finish the HTTP GET (bytes) request for URL: " + url, e); + return BinaryHttpResponse.empty(); } + + // HttpURLConnection does not follow redirects between different protocols (e.g., HTTP to HTTPS) + // reliably, so we handle redirects ourselves. AIA uses a small number of redirects, if any. for (int redirectCount = 0; redirectCount <= MAX_AIA_REDIRECTS; redirectCount++) { HttpURLConnection connection = null; + try { - connection = connectionFactory.open(currentUrl); + connection = connectionFactory.open(currentUrl); + connection.setInstanceFollowRedirects(false); connection.setRequestMethod("GET"); connection.setConnectTimeout(AIA_HTTP_TIMEOUT_IN_MILLISECONDS); @@ -121,31 +201,44 @@ static BinaryHttpResponse getBytesWithMetadata(String url, ConnectionFactory con String date = connection.getHeaderField("Date"); String age = connection.getHeaderField("Age"); String expires = connection.getHeaderField("Expires"); + if (isRedirect(status)) { String location = connection.getHeaderField("Location"); + if (location == null || redirectCount == MAX_AIA_REDIRECTS) { LOGGER.log(WARNING, "HTTP GET redirect could not be followed for URL: {0}", currentUrl); + return new BinaryHttpResponse(null, cacheControl, date, age, expires); } + currentUrl = resolveAiaRedirect(currentUrl, location); + continue; } + if (status < 200 || status >= 300) { LOGGER.log(WARNING, "HTTP GET returned status {0} for URL: {1}", new Object[] { status, currentUrl }); + return new BinaryHttpResponse(null, cacheControl, date, age, expires); } long contentLength = connection.getContentLengthLong(); + if (contentLength > MAX_AIA_RESPONSE_SIZE_IN_BYTES) { LOGGER.log(WARNING, "AIA response exceeded the maximum size for URL: {0}", currentUrl); + return new BinaryHttpResponse(null, cacheControl, date, age, expires); } return new BinaryHttpResponse(readResponseBytes(connection.getInputStream(), currentUrl), cacheControl, date, age, expires); } catch (IOException | IllegalArgumentException | ClassCastException | UncheckedIOException e) { + // Catch all exceptions including IOException, IllegalArgumentException, and other runtime exceptions + // that may occur during HTTP execution. Gracefully return null to allow AIA completion to fail silently + // the entire jarsigner/signing operation. LOGGER.log(WARNING, "Unable to finish the HTTP GET (bytes) request for URL: " + currentUrl, e); + return BinaryHttpResponse.empty(); } finally { if (connection != null) { @@ -153,6 +246,7 @@ static BinaryHttpResponse getBytesWithMetadata(String url, ConnectionFactory con } } } + return BinaryHttpResponse.empty(); } @@ -168,66 +262,85 @@ private static String resolveAiaRedirect(String currentUrl, String location) { if (location.startsWith("?")) { int queryIndex = currentUrl.indexOf('?'); int fragmentIndex = currentUrl.indexOf('#'); - int suffixIndex - = queryIndex < 0 ? fragmentIndex : fragmentIndex < 0 ? queryIndex : Math.min(queryIndex, fragmentIndex); + int suffixIndex = + queryIndex < 0 ? fragmentIndex : fragmentIndex < 0 ? queryIndex : Math.min(queryIndex, fragmentIndex); String currentUrlWithoutSuffix = suffixIndex < 0 ? currentUrl : currentUrl.substring(0, suffixIndex); + return validateAiaUrl(currentUrlWithoutSuffix + location); } + return validateAiaUrl(URI.create(currentUrl).resolve(location).toString()); } private static String validateAiaUrl(String url) { URI uri = URI.create(url); String scheme = uri.getScheme(); + if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) { throw new IllegalArgumentException("AIA URL must use HTTP or HTTPS."); } + return uri.toString(); } private static byte[] readResponseBytes(InputStream inputStream, String url) throws IOException { long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(AIA_HTTP_TOTAL_TIMEOUT_IN_MILLISECONDS); + try (InputStream responseBody = inputStream; ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { byte[] buffer = new byte[4096]; int totalBytesRead = 0; int read; + while ((read = responseBody.read(buffer)) != -1) { if (read > MAX_AIA_RESPONSE_SIZE_IN_BYTES - totalBytesRead) { LOGGER.log(WARNING, "AIA response exceeded the maximum size for URL: {0}", url); + return null; } + outputStream.write(buffer, 0, read); + totalBytesRead += read; + if (System.nanoTime() > deadline) { LOGGER.log(WARNING, "AIA response exceeded the maximum download time for URL: {0}", url); + return null; } } + return outputStream.toByteArray(); } } private static String getCombinedHeaderValue(HttpURLConnection connection, String name) { Map> headers = connection.getHeaderFields(); + if (headers == null) { - return connection.getHeaderField(name); + return null; } + StringBuilder value = new StringBuilder(); + for (Map.Entry> entry : headers.entrySet()) { if (entry.getKey() == null || !name.equalsIgnoreCase(entry.getKey()) || entry.getValue() == null) { continue; } + for (String headerValue : entry.getValue()) { if (headerValue == null) { continue; } + if (value.length() > 0) { value.append(", "); } + value.append(headerValue); } } - return value.length() == 0 ? connection.getHeaderField(name) : value.toString(); + + return value.length() == 0 ? null : value.toString(); } static final class BinaryHttpResponse { @@ -269,16 +382,7 @@ String getExpires() { return expires; } } - - public static String post(String uri, String body, String contentType) { - return post(uri, null, body, contentType); - } - - @FunctionalInterface - interface ConnectionFactory { - HttpURLConnection open(String url) throws IOException; - } - + public static String getUserAgentPrefix() { return Optional.of(HttpUtil.class) .map(Class::getClassLoader) @@ -291,86 +395,66 @@ public static String getUserAgentPrefix() { .orElse(DEFAULT_USER_AGENT_VALUE_PREFIX); } - public static String post(String uri, Map headers, String body, String contentType) { - return post(uri, headers, body, contentType, HttpUtil::openConnection); - } - - static String post(String uri, Map headers, String body, String contentType, - ConnectionFactory connectionFactory) { - HttpURLConnection connection = null; - try { - connection = connectionFactory.open(uri); - connection.setRequestMethod("POST"); - connection.setDoOutput(true); - - if (headers != null) { - headers.forEach(connection::setRequestProperty); - } - if (contentType != null) { - connection.setRequestProperty("Content-Type", contentType); - } - connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); - try (OutputStream outputStream = connection.getOutputStream()) { - outputStream.write(body.getBytes(StandardCharsets.UTF_8)); - } - - ensureSuccessfulResponse(connection.getResponseCode()); - return readResponseBody(connection); - } catch (IOException ioe) { - LOGGER.log(WARNING, "Unable to finish the HTTP POST request.", ioe); - return null; - } finally { - if (connection != null) { - connection.disconnect(); - } - } - } - private static String createErrorMessage(int status) { - return "Fail to get response from Key Vault because return http status code is " + status + ". It can be " + return "Failed to get response from Key Vault because return http status code is " + status + ". It can be " + "caused by missing permissions or roles. To know how to add permissions or roles, see " + "https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/keyvault/azure-security-keyvault-jca#prerequisites."; } - private static void ensureSuccessfulResponse(int status) { - if (status < 200 || status >= 300) { - String errorMessage = createErrorMessage(status); - LOGGER.log(SEVERE, errorMessage); - throw new RuntimeException(errorMessage); + private static boolean isSuccessfulResponse(int status) { + if (status >= 200 && status < 300) { + return true; } + + LOGGER.log(SEVERE, createErrorMessage(status)); + + return false; } @SuppressWarnings("StringOperationCanBeSimplified") private static String readResponseBody(HttpURLConnection connection) throws IOException { try (InputStream responseBody = connection.getInputStream(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + if (responseBody == null) { + return null; } + byte[] buffer = new byte[4096]; int read; + while ((read = responseBody.read(buffer)) != -1) { outputStream.write(buffer, 0, read); } + return new String(outputStream.toByteArray(), StandardCharsets.UTF_8); } } - public static Map> getWithResponseHeadersOnlyReturn(String uri) { - return getWithResponseHeadersOnlyReturn(uri, HttpUtil::openConnection); + /** + * Retrieves only the response headers from an HTTP GET request to the specified URI. + * + * @param uri the URI to send the HTTP GET request to + * @return a map of response headers, or null if the request was not successful + */ + public static Map> getWithOnlyResponseHeaders(String uri) { + return getWithOnlyResponseHeaders(uri, HttpUtil::openConnection); } - static Map> getWithResponseHeadersOnlyReturn(String uri, ConnectionFactory connectionFactory) { + static Map> getWithOnlyResponseHeaders(String uri, ConnectionFactory connectionFactory) { HttpURLConnection connection = null; + try { connection = connectionFactory.open(uri); - connection.setRequestMethod("GET"); + connection.setRequestMethod("GET"); connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); if (connection.getResponseCode() == 401) { Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); Map> responseHeaders = connection.getHeaderFields(); + if (responseHeaders != null) { responseHeaders.forEach((name, values) -> { if (name != null) { @@ -378,11 +462,14 @@ static Map> getWithResponseHeadersOnlyReturn(String uri, Co } }); } + return headers; } + return null; } catch (IOException ioe) { LOGGER.log(WARNING, "Unable to finish the HTTP GET request.", ioe); + return null; } finally { if (connection != null) { @@ -394,12 +481,16 @@ static Map> getWithResponseHeadersOnlyReturn(String uri, Co private static HttpURLConnection openConnection(String uri) { try { HttpURLConnection connection = (HttpURLConnection) URI.create(uri).toURL().openConnection(); + if (connection instanceof HttpsURLConnection) { try { - TrustManagerFactory trustManagerFactory - = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(JreKeyStoreFactory.getDefaultKeyStore()); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustManagerFactory.getTrustManagers(), null); ((HttpsURLConnection) connection).setSSLSocketFactory(sslContext.getSocketFactory()); } catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java index 8ccfc915c5b7..6183339d122b 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java @@ -1260,14 +1260,17 @@ private synchronized void sequence(K key, List> suppliers) { private void returnValues(K key, List values) { List> suppliers = new ArrayList<>(); + for (V value : values) { suppliers.add(() -> value); } + sequence(key, suppliers); } private void throwThenReturn(K key, RuntimeException exception, V value) { List> suppliers = new ArrayList<>(); + suppliers.add(() -> { throw exception; }); @@ -1281,6 +1284,7 @@ private void answer(K key, Supplier supplier) { private void answerThenReturn(K key, Supplier supplier, V value) { List> suppliers = new ArrayList<>(); + suppliers.add(supplier); suppliers.add(() -> value); sequence(key, suppliers); @@ -1288,23 +1292,31 @@ private void answerThenReturn(K key, Supplier supplier, V value) { private int callCount(K key) { AtomicInteger counter; + synchronized (this) { counter = callCounts.get(key); } + return counter == null ? 0 : counter.get(); } private V invoke(K key) { Supplier supplier; + synchronized (this) { AtomicInteger counter = callCounts.computeIfAbsent(key, unused -> new AtomicInteger()); + counter.incrementAndGet(); + Deque> suppliers = queuedSuppliers.get(key); + if (suppliers == null || suppliers.isEmpty()) { return null; } + supplier = suppliers.size() > 1 ? suppliers.poll() : suppliers.peek(); } + // The actual supplier invocation happens outside the synchronized block so blocking suppliers used by // concurrency tests don't serialize unrelated calls against this same script. return supplier.get(); 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 a37dafcde0e1..ff3a2b03abf7 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 @@ -58,8 +58,8 @@ public void testHttpUtilGet1() { @Test void textGetReturnsSuccessfulResponseBody() throws Exception { byte[] body = "response".getBytes(StandardCharsets.UTF_8); - TestHttpURLConnection connection - = new TestHttpURLConnection("https://example.test/value", 200, body, Collections.emptyMap()); + TestHttpURLConnection connection = + new TestHttpURLConnection("https://example.test/value", 200, body, Collections.emptyMap()); String result = HttpUtil.get("https://example.test/value", Collections.singletonMap("x-test", "value"), ignored -> connection); @@ -83,34 +83,34 @@ void textGetThrowsForNonSuccessfulResponse() throws Exception { } @Test - void postThrowsForNonSuccessfulResponse() throws Exception { + void postReturnsNullForNonSuccessfulResponse() throws Exception { TestHttpURLConnection connection = new TestHttpURLConnection("https://example.test/value", 403, "{\"error\":\"forbidden\"}".getBytes(StandardCharsets.UTF_8), Collections.emptyMap()); - RuntimeException exception = assertThrows(RuntimeException.class, () -> HttpUtil - .post("https://example.test/value", null, "request", "application/json", ignored -> connection)); + String result = HttpUtil + .post("https://example.test/value", null, "request", "application/json", ignored -> connection); - assertTrue(exception.getMessage().contains("403")); + assertNull(result); assertEquals("request", new String(connection.requestBody.toByteArray(), StandardCharsets.UTF_8)); assertTrue(connection.disconnected); } @Test void authenticationChallengeReturnsCaseInsensitiveHeadersOnlyFor401() throws Exception { - Map> headers - = Collections.singletonMap("www-authenticate", Collections.singletonList("Bearer authorization=test")); - TestHttpURLConnection unauthorized - = new TestHttpURLConnection("https://example.test/challenge", 401, new byte[0], headers); + Map> headers = + Collections.singletonMap("www-authenticate", Collections.singletonList("Bearer authorization=test")); + TestHttpURLConnection unauthorized = + new TestHttpURLConnection("https://example.test/challenge", 401, new byte[0], headers); - Map> result - = HttpUtil.getWithResponseHeadersOnlyReturn("https://example.test/challenge", ignored -> unauthorized); + Map> result = + HttpUtil.getWithOnlyResponseHeaders("https://example.test/challenge", ignored -> unauthorized); assertEquals("Bearer authorization=test", result.get("WWW-Authenticate").get(0)); assertTrue(unauthorized.disconnected); - TestHttpURLConnection successful - = new TestHttpURLConnection("https://example.test/challenge", 200, new byte[0], headers); - assertNull(HttpUtil.getWithResponseHeadersOnlyReturn("https://example.test/challenge", ignored -> successful)); + TestHttpURLConnection successful = + new TestHttpURLConnection("https://example.test/challenge", 200, new byte[0], headers); + assertNull(HttpUtil.getWithOnlyResponseHeaders("https://example.test/challenge", ignored -> successful)); assertTrue(successful.disconnected); } @@ -124,8 +124,8 @@ void binaryResponsePreservesBodyAndFreshnessHeaders() throws Exception { headers.put("Expires", Collections.singletonList("Wed, 05 Aug 2026 10:05:00 GMT")); TestHttpURLConnection connection = new TestHttpURLConnection(200, body, headers); - HttpUtil.BinaryHttpResponse result - = HttpUtil.getBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); + HttpUtil.BinaryHttpResponse result = + HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); assertArrayEquals(body, result.getBody()); assertEquals("public, max-age=300", result.getCacheControl()); @@ -140,12 +140,12 @@ void binaryResponsePreservesBodyAndFreshnessHeaders() throws Exception { @Test void binaryResponseForFailureHasNoBodyAndPreservesFreshnessMetadata() throws Exception { - Map> headers - = Collections.singletonMap("Cache-Control", Collections.singletonList("max-age=3600")); + Map> headers = + Collections.singletonMap("Cache-Control", Collections.singletonList("max-age=3600")); TestHttpURLConnection connection = new TestHttpURLConnection(503, new byte[] { 1 }, headers); - HttpUtil.BinaryHttpResponse result - = HttpUtil.getBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); + HttpUtil.BinaryHttpResponse result = + HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); assertNull(result.getBody()); assertEquals("max-age=3600", result.getCacheControl()); @@ -160,8 +160,8 @@ void binaryResponseCombinesMultipleCacheControlHeaders() throws Exception { headers.put("cache-control", Arrays.asList("public, max-age=300", "no-store")); TestHttpURLConnection connection = new TestHttpURLConnection(200, new byte[] { 1 }, headers); - HttpUtil.BinaryHttpResponse result - = HttpUtil.getBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); + HttpUtil.BinaryHttpResponse result = + HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); assertEquals("public, max-age=300, no-store", result.getCacheControl()); } @@ -172,8 +172,8 @@ void binaryResponseRejectsOversizedContentLength() throws Exception { Collections.singletonList(String.valueOf(HttpUtil.MAX_AIA_RESPONSE_SIZE_IN_BYTES + 1))); TestHttpURLConnection connection = new TestHttpURLConnection(200, new byte[] { 1 }, headers); - HttpUtil.BinaryHttpResponse result - = HttpUtil.getBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); + HttpUtil.BinaryHttpResponse result = + HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); assertNull(result.getBody()); assertTrue(connection.disconnected); @@ -184,8 +184,8 @@ void binaryResponseRejectsStreamThatExceedsMaximumSize() throws Exception { byte[] body = new byte[HttpUtil.MAX_AIA_RESPONSE_SIZE_IN_BYTES + 1]; TestHttpURLConnection connection = new TestHttpURLConnection(200, body, Collections.emptyMap()); - HttpUtil.BinaryHttpResponse result - = HttpUtil.getBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); + HttpUtil.BinaryHttpResponse result = + HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); assertNull(result.getBody()); assertTrue(connection.disconnected); @@ -195,13 +195,13 @@ void binaryResponseRejectsStreamThatExceedsMaximumSize() throws Exception { void binaryResponseFollowsHttpToHttpsRedirect() throws Exception { String sourceUrl = "http://example.test/cert.crt"; String targetUrl = "https://example.test/cert.crt"; - Map> redirectHeaders - = Collections.singletonMap("Location", Collections.singletonList(targetUrl)); + Map> redirectHeaders = + Collections.singletonMap("Location", Collections.singletonList(targetUrl)); TestHttpURLConnection redirect = new TestHttpURLConnection(sourceUrl, 302, new byte[0], redirectHeaders); byte[] body = new byte[] { 1, 2, 3 }; TestHttpURLConnection response = new TestHttpURLConnection(targetUrl, 200, body, Collections.emptyMap()); - HttpUtil.BinaryHttpResponse result = HttpUtil.getBytesWithMetadata(sourceUrl, url -> { + HttpUtil.BinaryHttpResponse result = HttpUtil.getAiaBytesWithMetadata(sourceUrl, url -> { if (sourceUrl.equals(url)) { return redirect; } @@ -221,13 +221,13 @@ void binaryResponseFollowsHttpToHttpsRedirect() throws Exception { void binaryResponsePreservesPathForQueryOnlyRedirect() throws Exception { String sourceUrl = "https://example.test/certificates/issuer.crt?v=1"; String targetUrl = "https://example.test/certificates/issuer.crt?v=2"; - Map> redirectHeaders - = Collections.singletonMap("Location", Collections.singletonList("?v=2")); + Map> redirectHeaders = + Collections.singletonMap("Location", Collections.singletonList("?v=2")); TestHttpURLConnection redirect = new TestHttpURLConnection(sourceUrl, 302, new byte[0], redirectHeaders); byte[] body = new byte[] { 1, 2, 3 }; TestHttpURLConnection response = new TestHttpURLConnection(targetUrl, 200, body, Collections.emptyMap()); - HttpUtil.BinaryHttpResponse result = HttpUtil.getBytesWithMetadata(sourceUrl, url -> { + HttpUtil.BinaryHttpResponse result = HttpUtil.getAiaBytesWithMetadata(sourceUrl, url -> { if (sourceUrl.equals(url)) { return redirect; } From 60ace7adc962893a68da301a124728fe44f91e3a Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Thu, 27 Aug 2026 02:42:35 -0700 Subject: [PATCH 10/16] Reverted some changes --- .../stress/scenarios/EventForwarder.java | 3 +- .../stress/util/TelemetryHelper.java | 2 +- .../jca/implementation/KeyVaultClient.java | 34 ++++++--- .../utils/AiaCertificateChainUtil.java | 4 +- .../jca/implementation/utils/HttpUtil.java | 76 ++++++++----------- .../KeyVaultCertificatesTest.java | 14 +--- .../implementation/utils/HttpUtilTest.java | 72 ++++++++++-------- 7 files changed, 103 insertions(+), 102 deletions(-) diff --git a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java index 4fd67886541d..907ecf0f6873 100644 --- a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java +++ b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java @@ -105,8 +105,7 @@ public void run() { private EventHubProducerAsyncClient getForwardProducer() { final TokenCredential tokenCredential = new DefaultAzureCredentialBuilder().build(); final EventHubClientBuilder builder = new EventHubClientBuilder() - .credential(options.getEventHubsFullyQualifiedNamespace(), forwardEventHubName, - tokenCredential) + .credential(options.getEventHubsFullyQualifiedNamespace(), forwardEventHubName, tokenCredential) .retryOptions(new AmqpRetryOptions().setTryTimeout(Duration.ofSeconds(10))) .transportType(options.getAmqpTransportType()) .consumerGroup(options.getEventHubsConsumerGroup()); diff --git a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java index 393155d8914e..0f9f6a37a768 100644 --- a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java +++ b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java @@ -90,7 +90,7 @@ public TelemetryHelper(Class scenarioClass) { */ private static OpenTelemetry init() { System.setProperty("otel.java.global-autoconfigure.enabled", "true"); - + AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder(); String applicationInsightsConnectionString = System.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"); if (applicationInsightsConnectionString == null) { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java index 86117827d975..aa10798b7d90 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java @@ -257,14 +257,13 @@ private AccessToken obtainAccessToken() { 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 @@ -274,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); @@ -298,7 +297,7 @@ public List getAliases() { 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) { @@ -344,7 +343,7 @@ private CertificateBundle getCertificateBundle(String alias) { LOGGER.entering("KeyVaultClient", "getCertificateBundle", alias); CertificateBundle result = null; - String response = HttpUtil.get(keyVaultUri + "certificates/" + alias + API_VERSION_POSTFIX, + String response = httpGet(keyVaultUri + "certificates/" + alias + API_VERSION_POSTFIX, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (response != null) { @@ -460,7 +459,7 @@ public Certificate[] getCertificateChainForVersion(CertificateVersion certificat return new Certificate[0]; } - String response = HttpUtil.get(certificateVersion.getSecretId() + API_VERSION_POSTFIX, + String response = httpGet(certificateVersion.getSecretId() + API_VERSION_POSTFIX, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (response == null) { @@ -541,7 +540,7 @@ public Key getKeyForVersion(CertificateVersion certificateVersion, char[] passwo return null; } - String body = HttpUtil.get(certificateSecretUri + API_VERSION_POSTFIX, + String body = httpGet(certificateSecretUri + API_VERSION_POSTFIX, Collections.singletonMap("Authorization", "Bearer " + getAccessToken())); if (body == null) { @@ -614,7 +613,7 @@ public byte[] getSignedWithPrivateKey(String digestName, String digestValue, Str String bodyString = "{\"alg\": \"" + digestName + "\", \"value\": \"" + digestValue + "\"}"; Map 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 { @@ -689,4 +688,21 @@ private PrivateKey createPrivateKeyFromPem(String pemString, String keyType) return privateKey; } + + String httpGet(String uri, Map headers) { + return HttpUtil.get(uri, headers); + } + + String httpPost(String uri, Map 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); + } } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java index 17c3688a5765..c34f9319d97d 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java @@ -55,8 +55,8 @@ final class AiaCertificateChainUtil { private static final int AIA_CACHE_MAX_SIZE = 128; private static final long MAX_SUCCESS_TTL_IN_MILLIS = TimeUnit.HOURS.toMillis(24); private static final long NEGATIVE_TTL_IN_MILLIS = TimeUnit.MINUTES.toMillis(1); - private static final AiaResponseCache AIA_CACHE = - new AiaResponseCache(AIA_CACHE_MAX_SIZE, System::currentTimeMillis, (message, parameters) -> LOGGER.logp(FINE, + private static final AiaResponseCache AIA_CACHE + = new AiaResponseCache(AIA_CACHE_MAX_SIZE, System::currentTimeMillis, (message, parameters) -> LOGGER.logp(FINE, AiaResponseCache.class.getName(), "diagnostic", message, parameters)); // A default HTTP-based response loader for AIA requests. private static final AiaResponseLoader DEFAULT_RESPONSE_LOADER = HttpUtil::getAiaBytesWithMetadata; 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 4014fe07cec7..04d517502f0c 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 @@ -51,6 +51,7 @@ public final class HttpUtil { private static final Logger LOGGER = Logger.getLogger(HttpUtil.class.getName()); + static final int HTTP_TIMEOUT_IN_MILLISECONDS = 180_000; private static final int AIA_HTTP_TIMEOUT_IN_MILLISECONDS = 10_000; static final int MAX_AIA_RESPONSE_SIZE_IN_BYTES = 10 * 1024 * 1024; private static final int AIA_HTTP_TOTAL_TIMEOUT_IN_MILLISECONDS = 30_000; @@ -67,6 +68,7 @@ interface ConnectionFactory { * @param uri the URI to send the GET request to * @param headers the headers to include in the request * @return the response body as a string, or {@code null} if the request fails + * @throws RuntimeException if the server returns a non-successful response */ public static String get(String uri, Map headers) { return get(uri, headers, HttpUtil::openConnection); @@ -78,20 +80,10 @@ static String get(String uri, Map headers, ConnectionFactory con try { connection = connectionFactory.open(uri); + configureConnection(connection, "GET", headers); - connection.setRequestMethod("GET"); - - if (headers != null) { - headers.forEach(connection::setRequestProperty); - } - - connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); - - if (isSuccessfulResponse(connection.getResponseCode())) { - return readResponseBody(connection); - } - - return null; + ensureSuccessfulResponse(connection.getResponseCode()); + return readResponseBody(connection); } catch (IOException ioe) { LOGGER.log(WARNING, "Unable to finish the HTTP GET request.", ioe); @@ -111,6 +103,7 @@ static String get(String uri, Map headers, ConnectionFactory con * @param body the body of the POST request * @param contentType the content type of the POST request body * @return the response body as a string, or {@code null} if the request fails + * @throws RuntimeException if the server returns a non-successful response */ public static String post(String uri, Map headers, String body, String contentType) { return post(uri, headers, body, contentType, HttpUtil::openConnection); @@ -124,29 +117,19 @@ static String post(String uri, Map headers, String body, String try { connection = connectionFactory.open(uri); - - connection.setRequestMethod("POST"); + configureConnection(connection, "POST", headers); connection.setDoOutput(true); - if (headers != null) { - headers.forEach(connection::setRequestProperty); - } - if (contentType != null) { connection.setRequestProperty("Content-Type", contentType); } - connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); - try (OutputStream outputStream = connection.getOutputStream()) { outputStream.write(body.getBytes(StandardCharsets.UTF_8)); } - if (isSuccessfulResponse(connection.getResponseCode())) { - return readResponseBody(connection); - } - - return null; + ensureSuccessfulResponse(connection.getResponseCode()); + return readResponseBody(connection); } catch (IOException ioe) { LOGGER.log(WARNING, "Unable to finish the HTTP POST request.", ioe); @@ -188,7 +171,7 @@ static BinaryHttpResponse getAiaBytesWithMetadata(String url, ConnectionFactory HttpURLConnection connection = null; try { - connection = connectionFactory.open(currentUrl); + connection = connectionFactory.open(currentUrl); connection.setInstanceFollowRedirects(false); connection.setRequestMethod("GET"); @@ -262,8 +245,8 @@ private static String resolveAiaRedirect(String currentUrl, String location) { if (location.startsWith("?")) { int queryIndex = currentUrl.indexOf('?'); int fragmentIndex = currentUrl.indexOf('#'); - int suffixIndex = - queryIndex < 0 ? fragmentIndex : fragmentIndex < 0 ? queryIndex : Math.min(queryIndex, fragmentIndex); + int suffixIndex + = queryIndex < 0 ? fragmentIndex : fragmentIndex < 0 ? queryIndex : Math.min(queryIndex, fragmentIndex); String currentUrlWithoutSuffix = suffixIndex < 0 ? currentUrl : currentUrl.substring(0, suffixIndex); return validateAiaUrl(currentUrlWithoutSuffix + location); @@ -382,7 +365,7 @@ String getExpires() { return expires; } } - + public static String getUserAgentPrefix() { return Optional.of(HttpUtil.class) .map(Class::getClassLoader) @@ -401,20 +384,18 @@ private static String createErrorMessage(int status) { + "https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/keyvault/azure-security-keyvault-jca#prerequisites."; } - private static boolean isSuccessfulResponse(int status) { - if (status >= 200 && status < 300) { - return true; + private static void ensureSuccessfulResponse(int status) { + if (status < 200 || status >= 300) { + String errorMessage = createErrorMessage(status); + LOGGER.log(SEVERE, errorMessage); + throw new RuntimeException(errorMessage); } - - LOGGER.log(SEVERE, createErrorMessage(status)); - - return false; } @SuppressWarnings("StringOperationCanBeSimplified") private static String readResponseBody(HttpURLConnection connection) throws IOException { try (InputStream responseBody = connection.getInputStream(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { if (responseBody == null) { @@ -447,9 +428,7 @@ static Map> getWithOnlyResponseHeaders(String uri, Connecti try { connection = connectionFactory.open(uri); - - connection.setRequestMethod("GET"); - connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); + configureConnection(connection, "GET", null); if (connection.getResponseCode() == 401) { Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); @@ -478,14 +457,25 @@ static Map> getWithOnlyResponseHeaders(String uri, Connecti } } + private static void configureConnection(HttpURLConnection connection, String method, Map headers) + throws IOException { + connection.setRequestMethod(method); + connection.setConnectTimeout(HTTP_TIMEOUT_IN_MILLISECONDS); + connection.setReadTimeout(HTTP_TIMEOUT_IN_MILLISECONDS); + if (headers != null) { + headers.forEach(connection::setRequestProperty); + } + connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); + } + private static HttpURLConnection openConnection(String uri) { try { HttpURLConnection connection = (HttpURLConnection) URI.create(uri).toURL().openConnection(); if (connection instanceof HttpsURLConnection) { try { - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + TrustManagerFactory trustManagerFactory + = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(JreKeyStoreFactory.getDefaultKeyStore()); diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java index 6183339d122b..30a725aea0ef 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/certificates/KeyVaultCertificatesTest.java @@ -1260,17 +1260,14 @@ private synchronized void sequence(K key, List> suppliers) { private void returnValues(K key, List values) { List> suppliers = new ArrayList<>(); - for (V value : values) { suppliers.add(() -> value); } - sequence(key, suppliers); } private void throwThenReturn(K key, RuntimeException exception, V value) { List> suppliers = new ArrayList<>(); - suppliers.add(() -> { throw exception; }); @@ -1284,7 +1281,6 @@ private void answer(K key, Supplier supplier) { private void answerThenReturn(K key, Supplier supplier, V value) { List> suppliers = new ArrayList<>(); - suppliers.add(supplier); suppliers.add(() -> value); sequence(key, suppliers); @@ -1292,31 +1288,23 @@ private void answerThenReturn(K key, Supplier supplier, V value) { private int callCount(K key) { AtomicInteger counter; - synchronized (this) { counter = callCounts.get(key); } - return counter == null ? 0 : counter.get(); } private V invoke(K key) { Supplier supplier; - synchronized (this) { AtomicInteger counter = callCounts.computeIfAbsent(key, unused -> new AtomicInteger()); - counter.incrementAndGet(); - Deque> suppliers = queuedSuppliers.get(key); - if (suppliers == null || suppliers.isEmpty()) { - return null; + throw new AssertionError("Unexpected call for key: " + key); } - supplier = suppliers.size() > 1 ? suppliers.poll() : suppliers.peek(); } - // The actual supplier invocation happens outside the synchronized block so blocking suppliers used by // concurrency tests don't serialize unrelated calls against this same script. return supplier.get(); 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 ff3a2b03abf7..23ff93a7bc2b 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 @@ -58,15 +58,19 @@ public void testHttpUtilGet1() { @Test void textGetReturnsSuccessfulResponseBody() throws Exception { byte[] body = "response".getBytes(StandardCharsets.UTF_8); - TestHttpURLConnection connection = - new TestHttpURLConnection("https://example.test/value", 200, body, Collections.emptyMap()); + TestHttpURLConnection connection + = new TestHttpURLConnection("https://example.test/value", 200, body, Collections.emptyMap()); + Map requestHeaders = new LinkedHashMap<>(); + requestHeaders.put("x-test", "value"); + requestHeaders.put(HttpUtil.USER_AGENT_KEY, "caller-value"); - String result = HttpUtil.get("https://example.test/value", Collections.singletonMap("x-test", "value"), - ignored -> connection); + String result = HttpUtil.get("https://example.test/value", requestHeaders, ignored -> connection); assertEquals("response", result); assertEquals("value", connection.getRequestProperty("x-test")); assertEquals(HttpUtil.USER_AGENT_VALUE, connection.getRequestProperty(HttpUtil.USER_AGENT_KEY)); + assertEquals(HttpUtil.HTTP_TIMEOUT_IN_MILLISECONDS, connection.getConnectTimeout()); + assertEquals(HttpUtil.HTTP_TIMEOUT_IN_MILLISECONDS, connection.getReadTimeout()); assertTrue(connection.disconnected); } @@ -83,33 +87,37 @@ void textGetThrowsForNonSuccessfulResponse() throws Exception { } @Test - void postReturnsNullForNonSuccessfulResponse() throws Exception { + void postThrowsForNonSuccessfulResponse() throws Exception { TestHttpURLConnection connection = new TestHttpURLConnection("https://example.test/value", 403, "{\"error\":\"forbidden\"}".getBytes(StandardCharsets.UTF_8), Collections.emptyMap()); - String result = HttpUtil - .post("https://example.test/value", null, "request", "application/json", ignored -> connection); + RuntimeException exception = assertThrows(RuntimeException.class, () -> HttpUtil + .post("https://example.test/value", null, "request", "application/json", ignored -> connection)); - assertNull(result); + assertTrue(exception.getMessage().contains("403")); assertEquals("request", new String(connection.requestBody.toByteArray(), StandardCharsets.UTF_8)); + assertEquals(HttpUtil.HTTP_TIMEOUT_IN_MILLISECONDS, connection.getConnectTimeout()); + assertEquals(HttpUtil.HTTP_TIMEOUT_IN_MILLISECONDS, connection.getReadTimeout()); assertTrue(connection.disconnected); } @Test void authenticationChallengeReturnsCaseInsensitiveHeadersOnlyFor401() throws Exception { - Map> headers = - Collections.singletonMap("www-authenticate", Collections.singletonList("Bearer authorization=test")); - TestHttpURLConnection unauthorized = - new TestHttpURLConnection("https://example.test/challenge", 401, new byte[0], headers); + Map> headers + = Collections.singletonMap("www-authenticate", Collections.singletonList("Bearer authorization=test")); + TestHttpURLConnection unauthorized + = new TestHttpURLConnection("https://example.test/challenge", 401, new byte[0], headers); - Map> result = - HttpUtil.getWithOnlyResponseHeaders("https://example.test/challenge", ignored -> unauthorized); + Map> result + = HttpUtil.getWithOnlyResponseHeaders("https://example.test/challenge", ignored -> unauthorized); assertEquals("Bearer authorization=test", result.get("WWW-Authenticate").get(0)); + assertEquals(HttpUtil.HTTP_TIMEOUT_IN_MILLISECONDS, unauthorized.getConnectTimeout()); + assertEquals(HttpUtil.HTTP_TIMEOUT_IN_MILLISECONDS, unauthorized.getReadTimeout()); assertTrue(unauthorized.disconnected); - TestHttpURLConnection successful = - new TestHttpURLConnection("https://example.test/challenge", 200, new byte[0], headers); + TestHttpURLConnection successful + = new TestHttpURLConnection("https://example.test/challenge", 200, new byte[0], headers); assertNull(HttpUtil.getWithOnlyResponseHeaders("https://example.test/challenge", ignored -> successful)); assertTrue(successful.disconnected); } @@ -124,8 +132,8 @@ void binaryResponsePreservesBodyAndFreshnessHeaders() throws Exception { headers.put("Expires", Collections.singletonList("Wed, 05 Aug 2026 10:05:00 GMT")); TestHttpURLConnection connection = new TestHttpURLConnection(200, body, headers); - HttpUtil.BinaryHttpResponse result = - HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); + HttpUtil.BinaryHttpResponse result + = HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); assertArrayEquals(body, result.getBody()); assertEquals("public, max-age=300", result.getCacheControl()); @@ -140,12 +148,12 @@ void binaryResponsePreservesBodyAndFreshnessHeaders() throws Exception { @Test void binaryResponseForFailureHasNoBodyAndPreservesFreshnessMetadata() throws Exception { - Map> headers = - Collections.singletonMap("Cache-Control", Collections.singletonList("max-age=3600")); + Map> headers + = Collections.singletonMap("Cache-Control", Collections.singletonList("max-age=3600")); TestHttpURLConnection connection = new TestHttpURLConnection(503, new byte[] { 1 }, headers); - HttpUtil.BinaryHttpResponse result = - HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); + HttpUtil.BinaryHttpResponse result + = HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); assertNull(result.getBody()); assertEquals("max-age=3600", result.getCacheControl()); @@ -160,8 +168,8 @@ void binaryResponseCombinesMultipleCacheControlHeaders() throws Exception { headers.put("cache-control", Arrays.asList("public, max-age=300", "no-store")); TestHttpURLConnection connection = new TestHttpURLConnection(200, new byte[] { 1 }, headers); - HttpUtil.BinaryHttpResponse result = - HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); + HttpUtil.BinaryHttpResponse result + = HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); assertEquals("public, max-age=300, no-store", result.getCacheControl()); } @@ -172,8 +180,8 @@ void binaryResponseRejectsOversizedContentLength() throws Exception { Collections.singletonList(String.valueOf(HttpUtil.MAX_AIA_RESPONSE_SIZE_IN_BYTES + 1))); TestHttpURLConnection connection = new TestHttpURLConnection(200, new byte[] { 1 }, headers); - HttpUtil.BinaryHttpResponse result = - HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); + HttpUtil.BinaryHttpResponse result + = HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); assertNull(result.getBody()); assertTrue(connection.disconnected); @@ -184,8 +192,8 @@ void binaryResponseRejectsStreamThatExceedsMaximumSize() throws Exception { byte[] body = new byte[HttpUtil.MAX_AIA_RESPONSE_SIZE_IN_BYTES + 1]; TestHttpURLConnection connection = new TestHttpURLConnection(200, body, Collections.emptyMap()); - HttpUtil.BinaryHttpResponse result = - HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); + HttpUtil.BinaryHttpResponse result + = HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> connection); assertNull(result.getBody()); assertTrue(connection.disconnected); @@ -195,8 +203,8 @@ void binaryResponseRejectsStreamThatExceedsMaximumSize() throws Exception { void binaryResponseFollowsHttpToHttpsRedirect() throws Exception { String sourceUrl = "http://example.test/cert.crt"; String targetUrl = "https://example.test/cert.crt"; - Map> redirectHeaders = - Collections.singletonMap("Location", Collections.singletonList(targetUrl)); + Map> redirectHeaders + = Collections.singletonMap("Location", Collections.singletonList(targetUrl)); TestHttpURLConnection redirect = new TestHttpURLConnection(sourceUrl, 302, new byte[0], redirectHeaders); byte[] body = new byte[] { 1, 2, 3 }; TestHttpURLConnection response = new TestHttpURLConnection(targetUrl, 200, body, Collections.emptyMap()); @@ -221,8 +229,8 @@ void binaryResponseFollowsHttpToHttpsRedirect() throws Exception { void binaryResponsePreservesPathForQueryOnlyRedirect() throws Exception { String sourceUrl = "https://example.test/certificates/issuer.crt?v=1"; String targetUrl = "https://example.test/certificates/issuer.crt?v=2"; - Map> redirectHeaders = - Collections.singletonMap("Location", Collections.singletonList("?v=2")); + Map> redirectHeaders + = Collections.singletonMap("Location", Collections.singletonList("?v=2")); TestHttpURLConnection redirect = new TestHttpURLConnection(sourceUrl, 302, new byte[0], redirectHeaders); byte[] body = new byte[] { 1, 2, 3 }; TestHttpURLConnection response = new TestHttpURLConnection(targetUrl, 200, body, Collections.emptyMap()); From 455beae7b69232b0147fc496fb0375296f1b2cd9 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Thu, 27 Aug 2026 02:51:01 -0700 Subject: [PATCH 11/16] Added a few comments --- .../jca/implementation/utils/AccessTokenUtil.java | 6 ++++++ .../jca/implementation/utils/AiaCertificateChainUtil.java | 8 ++++++-- .../keyvault/jca/implementation/utils/HttpUtil.java | 6 ++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java index 94a029acdd94..cbf7802488e8 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java @@ -130,6 +130,7 @@ public static AccessToken getAccessToken(String resource, String aadAuthenticati return getAccessToken(resource, aadAuthenticationUrl, tenantId, clientId, clientSecret, HttpUtil::post); } + // Overloaded method that allows specifying a custom HttpPoster for testing. static AccessToken getAccessToken(String resource, String aadAuthenticationUrl, String tenantId, String clientId, String clientSecret, HttpPoster httpPoster) { // The client secret is deliberately left out: entering() renders every parameter in clear text. @@ -168,6 +169,11 @@ static AccessToken getAccessToken(String resource, String aadAuthenticationUrl, return result; } + /** + * Functional interface for making HTTP POST requests. + * + *

Introduced to be used for testing purposes, allowing the HTTP POST behavior to be mocked or overridden. + */ @FunctionalInterface interface HttpPoster { String post(String uri, Map headers, String body, String contentType); diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java index c34f9319d97d..db0cc0f8c44e 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java @@ -602,7 +602,8 @@ static void clearAiaCache() { /** * Sets the response loader for AIA requests. * - *

This can be used to override the default HTTP-based loader, for example in tests. + *

Introduced to be used for testing purposes, allowing the HTTP response loading behavior to be mocked or + * overridden. * * @param loader the response loader to use */ @@ -613,7 +614,8 @@ static synchronized void setResponseLoader(AiaResponseLoader loader) { /** * Resets the response loader for AIA requests to the default HTTP-based loader. * - *

This can be used to undo any overrides set by {@link #setResponseLoader(AiaResponseLoader)}. + *

Introduced to be used for testing purposes, allowing the HTTP response loading behavior to be reset to the + * default. */ static synchronized void resetResponseLoader() { responseLoader = DEFAULT_RESPONSE_LOADER; @@ -621,6 +623,8 @@ static synchronized void resetResponseLoader() { /** * Functional interface for loading AIA responses. + * + *

Introduced to be used for testing purposes, allowing the HTTP POST behavior to be mocked or overridden. */ @FunctionalInterface interface AiaResponseLoader { 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 04d517502f0c..2e0c0dd15b54 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 @@ -57,6 +57,11 @@ public final class HttpUtil { private static final int AIA_HTTP_TOTAL_TIMEOUT_IN_MILLISECONDS = 30_000; private static final int MAX_AIA_REDIRECTS = 5; + /** + * Functional interface for opening HTTP connections. + * + *

Introduced to be used for testing purposes, allowing the HTTP connection behavior to be mocked or overridden. + */ @FunctionalInterface interface ConnectionFactory { HttpURLConnection open(String url) throws IOException; @@ -423,6 +428,7 @@ public static Map> getWithOnlyResponseHeaders(String uri) { return getWithOnlyResponseHeaders(uri, HttpUtil::openConnection); } + // Overloaded method that allows specifying a custom ConnectionFactory for testing purposes. static Map> getWithOnlyResponseHeaders(String uri, ConnectionFactory connectionFactory) { HttpURLConnection connection = null; From fd7c85807049cabbedd8004c786209f8b1f1353e Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Thu, 27 Aug 2026 14:07:04 -0700 Subject: [PATCH 12/16] Reverted formatting merge changes --- .../messaging/eventhubs/stress/scenarios/EventForwarder.java | 3 ++- .../azure/messaging/eventhubs/stress/util/TelemetryHelper.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java index 907ecf0f6873..4fd67886541d 100644 --- a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java +++ b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/scenarios/EventForwarder.java @@ -105,7 +105,8 @@ public void run() { private EventHubProducerAsyncClient getForwardProducer() { final TokenCredential tokenCredential = new DefaultAzureCredentialBuilder().build(); final EventHubClientBuilder builder = new EventHubClientBuilder() - .credential(options.getEventHubsFullyQualifiedNamespace(), forwardEventHubName, tokenCredential) + .credential(options.getEventHubsFullyQualifiedNamespace(), forwardEventHubName, + tokenCredential) .retryOptions(new AmqpRetryOptions().setTryTimeout(Duration.ofSeconds(10))) .transportType(options.getAmqpTransportType()) .consumerGroup(options.getEventHubsConsumerGroup()); diff --git a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java index 0f9f6a37a768..393155d8914e 100644 --- a/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java +++ b/sdk/eventhubs/azure-messaging-eventhubs-stress/src/main/java/com/azure/messaging/eventhubs/stress/util/TelemetryHelper.java @@ -90,7 +90,7 @@ public TelemetryHelper(Class scenarioClass) { */ private static OpenTelemetry init() { System.setProperty("otel.java.global-autoconfigure.enabled", "true"); - + AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder(); String applicationInsightsConnectionString = System.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"); if (applicationInsightsConnectionString == null) { From d18315eb253870f03fb326fa1d5ff749f52b9140 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 28 Aug 2026 16:12:56 -0700 Subject: [PATCH 13/16] Updated samples to use HttpUrlConnection --- .../azure-security-keyvault-jca/README.md | 164 ++++++++++++++++-- .../security/keyvault/jca/SampleUtils.java | 98 ----------- .../jca/TrustSelfSignedServerDelegate.java | 50 ------ .../keyvault/jca/mtls/ClientMTLSSample.java | 92 ++++++++-- .../keyvault/jca/mtls/ServerMTLSSample.java | 9 + .../keyvault/jca/tls/ClientSSLSample.java | 77 +++++++- .../keyvault/jca/tls/ServerSSLSample.java | 6 + 7 files changed, 313 insertions(+), 183 deletions(-) delete mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/SampleUtils.java delete mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/TrustSelfSignedServerDelegate.java diff --git a/sdk/keyvault/azure-security-keyvault-jca/README.md b/sdk/keyvault/azure-security-keyvault-jca/README.md index ed8a36a5a86d..8d508ca54631 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/README.md +++ b/sdk/keyvault/azure-security-keyvault-jca/README.md @@ -216,13 +216,17 @@ System.setProperty("azure.keyvault.client-id", "" System.setProperty("azure.keyvault.client-secret", ""); KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); +// Register the provider before requesting its KeyStore implementation. Security.addProvider(provider); +// Load the certificate and private key that identify this server to connecting clients. KeyStore keyStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); +// Key managers select the server certificate and private key during each TLS handshake. KeyManagerFactory managerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); managerFactory.init(keyStore, "".toCharArray()); +// Configure one-way TLS: clients aren't required to present a certificate. SSLContext context = SSLContext.getInstance("TLS"); context.init(managerFactory.getKeyManagers(), null, null); @@ -230,11 +234,13 @@ SSLServerSocketFactory socketFactory = context.getServerSocketFactory(); SSLServerSocket serverSocket = (SSLServerSocket) socketFactory.createServerSocket(8765); while (true) { + // Accept a TLS connection and write a minimal HTTP response over it. SSLSocket socket = (SSLSocket) serverSocket.accept(); System.out.println("Client connected: " + socket.getInetAddress()); BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String body = "Hello, this is server."; + // Build a minimal HTTP response and calculate Content-Length from the UTF-8 body bytes. String response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: " + body.getBytes(StandardCharsets.UTF_8).length + "\r\nConnection: close\r\n\r\n" + body; @@ -247,7 +253,7 @@ while (true) { **Note:** See [Authentication Methods](#authentication-methods) for configuration details. #### Client side SSL -If you are looking to integrate the JCA provider for client side socket connections, see the Apache HTTP client example below. +If you are looking to integrate the JCA provider for client side socket connections, see the HTTPS URL connection example below. ```java readme-sample-clientSSL System.setProperty("azure.keyvault.uri", ""); @@ -256,14 +262,47 @@ System.setProperty("azure.keyvault.client-id", "" System.setProperty("azure.keyvault.client-secret", ""); KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); +// Register the provider before requesting its KeyStore implementation. Security.addProvider(provider); KeyStore keyStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); -// This section initializing SSLContext can be replaced with implementation specific consumption of 'KeyStore', -// if the library being used has convenience methods for that. +// Create trust managers from the certificates in the Key Vault-backed KeyStore. +TrustManagerFactory trustManagerFactory + = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); +trustManagerFactory.init(keyStore); +TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + +// The local server may use a self-signed certificate. Accept a one-certificate server chain while delegating +// validation of all other chains to the platform trust manager. Do not use this behavior in production. +for (int i = 0; i < trustManagers.length; i++) { + if (trustManagers[i] instanceof X509TrustManager) { + X509TrustManager delegate = (X509TrustManager) trustManagers[i]; + trustManagers[i] = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + delegate.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + if (chain.length != 1) { + delegate.checkServerTrusted(chain, authType); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return delegate.getAcceptedIssuers(); + } + }; + } +} + +// Configure one-way TLS: the client validates the server but doesn't present a client certificate. SSLContext sslContext = SSLContext.getInstance("TLS"); -TrustManager[] trustManagers = SampleUtils.loadTrustMaterial(keyStore); sslContext.init(null, trustManagers, null); String result = null; @@ -272,19 +311,40 @@ try { // openConnection will return HttpsURLConnection when the protocol is 'https'. connection = (HttpsURLConnection) URI.create("https://localhost:8765").toURL().openConnection(); - // Have the HttpsURLConnection use the SSLSocketFactory returned by SSLContext. + // Apply the custom trust configuration to this HTTPS connection. connection.setSSLSocketFactory(sslContext.getSocketFactory()); + // Allow the sample certificate to use a hostname other than localhost. Do not do this in production. + connection.setHostnameVerifier((hostname, session) -> true); connection.setRequestMethod("GET"); int status = connection.getResponseCode(); if (status == 200) { - result = SampleUtils.readResponse(connection); + // Decode the response using its declared charset, or UTF-8 when no charset is present. + Charset responseCharset = StandardCharsets.UTF_8; + String contentType = connection.getContentType(); + if (contentType != null) { + Matcher matcher = Pattern.compile("(?i)\\bcharset\\s*=\\s*\"?([^;\\s\"]+)") + .matcher(contentType); + if (matcher.find()) { + responseCharset = Charset.forName(matcher.group(1)); + } + } + + // Read the complete body without changing its line endings. + try (Reader reader = new InputStreamReader(connection.getInputStream(), responseCharset)) { + StringBuilder responseBody = new StringBuilder(); + char[] buffer = new char[1024]; + int read; + while ((read = reader.read(buffer)) != -1) { + responseBody.append(buffer, 0, read); + } + result = responseBody.toString(); + } } else { result = "Not success"; } } catch (IOException ioe) { ioe.printStackTrace(); - result = "Not success"; } finally { if (connection != null) { connection.disconnect(); @@ -301,14 +361,17 @@ If you are looking to integrate the JCA provider to create an SSLServerSocket se ```java readme-sample-serverMTLS KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); +// Register the provider before requesting its KeyStore implementation. Security.addProvider(provider); System.setProperty("azure.keyvault.uri", ""); System.setProperty("azure.keyvault.tenant-id", ""); System.setProperty("azure.keyvault.client-id", ""); System.setProperty("azure.keyvault.client-secret", ""); +// Load the certificate and private key that identify this server to connecting clients. KeyStore keyStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); +// Key managers select the server certificate and private key during each mTLS handshake. KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, "".toCharArray()); @@ -316,24 +379,30 @@ System.setProperty("azure.keyvault.uri", ""); System.setProperty("azure.keyvault.tenant-id", ""); System.setProperty("azure.keyvault.client-id", ""); System.setProperty("azure.keyvault.client-secret", ""); +// Load the client certificates that this server trusts. KeyStore trustStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); +// Trust managers validate the certificate presented by each client. TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); +// Combine the server identity with the client trust configuration. SSLContext context = SSLContext.getInstance("TLS"); context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); SSLServerSocketFactory socketFactory = context.getServerSocketFactory(); SSLServerSocket serverSocket = (SSLServerSocket) socketFactory.createServerSocket(8765); +// Require every client to present a trusted certificate during the TLS handshake. serverSocket.setNeedClientAuth(true); while (true) { + // Accept an mTLS connection and write a minimal HTTP response over it. SSLSocket socket = (SSLSocket) serverSocket.accept(); System.out.println("Client connected: " + socket.getInetAddress()); BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String body = "Hello, this is server."; + // Build a minimal HTTP response and calculate Content-Length from the UTF-8 body bytes. String response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: " + body.getBytes(StandardCharsets.UTF_8).length + "\r\nConnection: close\r\n\r\n" + body; @@ -346,55 +415,117 @@ while (true) { **Note:** See [Authentication Methods](#authentication-methods) for configuration details. #### Client side mTLS -If you are looking to integrate the JCA provider for client side socket connections, see the Apache HTTP client example below. +If you are looking to integrate the JCA provider for client side socket connections, see the HTTPS URL connection example below. ```java readme-sample-clientMTLS KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); +// Register the provider before requesting its KeyStore implementation. Security.addProvider(provider); System.setProperty("azure.keyvault.uri", ""); System.setProperty("azure.keyvault.tenant-id", ""); System.setProperty("azure.keyvault.client-id", ""); System.setProperty("azure.keyvault.client-secret", ""); +// Load the certificate and private key that identify this client to the server. KeyStore keyStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); System.setProperty("azure.keyvault.uri", ""); System.setProperty("azure.keyvault.tenant-id", ""); System.setProperty("azure.keyvault.client-id", ""); System.setProperty("azure.keyvault.client-secret", ""); +// Load the server certificates that this client trusts. KeyStore trustStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); -// This section initializing SSLContext can be replaced with implementation specific consumption of 'KeyStore', -// if the library being used has convenience methods for that. +// Create trust managers from the server trust material. +TrustManagerFactory trustManagerFactory + = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); +trustManagerFactory.init(trustStore); +TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + +// The local server may use a self-signed certificate. Accept a one-certificate server chain while delegating +// validation of all other chains to the platform trust manager. Do not use this behavior in production. +for (int i = 0; i < trustManagers.length; i++) { + if (trustManagers[i] instanceof X509TrustManager) { + X509TrustManager delegate = (X509TrustManager) trustManagers[i]; + trustManagers[i] = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + delegate.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + if (chain.length != 1) { + delegate.checkServerTrusted(chain, authType); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return delegate.getAcceptedIssuers(); + } + }; + } +} + +// Create key managers that select the client certificate and private key during the mTLS handshake. +KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); +keyManagerFactory.init(keyStore, "".toCharArray()); + +// Combine the client identity with the server trust configuration. SSLContext sslContext = SSLContext.getInstance("TLS"); -TrustManager[] trustManagers = SampleUtils.loadTrustMaterial(keyStore); -KeyManager[] keyManagers = SampleUtils.loadKeyMaterial(keyStore, "".toCharArray()); -sslContext.init(keyManagers, trustManagers, null); +sslContext.init(keyManagerFactory.getKeyManagers(), trustManagers, null); String result = null; HttpsURLConnection connection = null; + try { // openConnection will return HttpsURLConnection when the protocol is 'https'. connection = (HttpsURLConnection) URI.create("https://localhost:8765").toURL().openConnection(); - // Have the HttpsURLConnection use the SSLSocketFactory returned by SSLContext. + // Apply the custom identity and trust configuration to this HTTPS connection. connection.setSSLSocketFactory(sslContext.getSocketFactory()); + // Allow the sample certificate to use a hostname other than localhost. Do not do this in production. + connection.setHostnameVerifier((hostname, session) -> true); connection.setRequestMethod("GET"); int status = connection.getResponseCode(); + if (status == 200) { - result = SampleUtils.readResponse(connection); + // Decode the response using its declared charset, or UTF-8 when no charset is present. + Charset responseCharset = StandardCharsets.UTF_8; + String contentType = connection.getContentType(); + if (contentType != null) { + Matcher matcher = Pattern.compile("(?i)\\bcharset\\s*=\\s*\"?([^;\\s\"]+)") + .matcher(contentType); + if (matcher.find()) { + responseCharset = Charset.forName(matcher.group(1)); + } + } + + // Read the complete body without changing its line endings. + try (Reader reader = new InputStreamReader(connection.getInputStream(), responseCharset)) { + StringBuilder responseBody = new StringBuilder(); + char[] buffer = new char[1024]; + int read; + while ((read = reader.read(buffer)) != -1) { + responseBody.append(buffer, 0, read); + } + result = responseBody.toString(); + } } else { result = "Not success"; } } catch (IOException ioe) { ioe.printStackTrace(); - result = "Not success"; } finally { if (connection != null) { connection.disconnect(); } } + System.out.println(result); ``` @@ -737,4 +868,3 @@ This project has adopted the [Microsoft Open Source Code of Conduct][microsoft_c [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/samples/java/com/azure/security/keyvault/jca/SampleUtils.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/SampleUtils.java deleted file mode 100644 index f280959df1ec..000000000000 --- a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/SampleUtils.java +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -package com.azure.security.keyvault.jca; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.nio.charset.StandardCharsets; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; - -/** - * Utility methods for samples. - */ -public final class SampleUtils { - /** - * Loads the {@link TrustManager TrustManagers} for the {@link KeyStore}. - *

- * This wraps {@link X509TrustManager X509TrustManagers} with {@link TrustSelfSignedServerDelegate} to support - * self-signed certificates. - * - * @param keyStore The {@link KeyStore} where {@link TrustManager TrustManagers} will be loaded. - * @return The {@link TrustManager TrustManagers} that were loaded. - * @throws NoSuchAlgorithmException If the algorithm used when calling - * {@link TrustManagerFactory#getInstance(String)} isn't available. - * @throws KeyStoreException If calling {@link TrustManagerFactory#init(KeyStore)} fails. - */ - public static TrustManager[] loadTrustMaterial(KeyStore keyStore) throws NoSuchAlgorithmException, - KeyStoreException { - TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmFactory.init(keyStore); - TrustManager[] trustManagers = tmFactory.getTrustManagers(); - if (trustManagers != null) { - for (int i = 0; i < trustManagers.length; i++) { - TrustManager trustManager = trustManagers[i]; - if (trustManager instanceof X509TrustManager) { - // Wrap X509TrustManagers with an implementation that trusts self-signed certificates. - // This doesn't need to be done and is just an example. - trustManagers[i] = new TrustSelfSignedServerDelegate((X509TrustManager) trustManager); - } - } - } - - return trustManagers; - } - - /** - * Loads the {@link KeyManager KeyManagers} for the {@link KeyStore}. - * - * @param keyStore The {@link KeyStore} where {@link KeyManager KeyManagers} will be loaded. - * @param password The password for recovering {@link KeyManager KeyManagers} in the {@link KeyStore}. - * @return The {@link KeyManager KeyManagers} that were loaded. - * @throws NoSuchAlgorithmException If the algorithm used when calling {@link KeyManagerFactory#getInstance(String)} - * isn't available. - * @throws KeyStoreException If calling {@link KeyManagerFactory#init(KeyStore, char[])} fails. - * @throws UnrecoverableKeyException If the {@link KeyManager} can't be recovered when calling - * {@link KeyManagerFactory#init(KeyStore, char[])}, such as the {@code password is wrong}. - */ - public static KeyManager[] loadKeyMaterial(KeyStore keyStore, char[] password) - throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { - KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmFactory.init(keyStore, password); - return kmFactory.getKeyManagers(); - } - - /** - * Reads the {@link HttpURLConnection} response body to a string. - * - * @param connection The {@link HttpURLConnection} to read the response body for. - * @return The response body as a string. - * @throws IOException If an I/O error occurs while reading the response body. - */ - @SuppressWarnings("StringOperationCanBeSimplified") - public static String readResponse(HttpURLConnection connection) throws IOException { - InputStream response = (connection.getInputStream() != null) - ? connection.getInputStream() - : connection.getErrorStream(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - byte[] buffer = new byte[4096]; - int read; - while ((read = response.read(buffer)) != -1) { - outputStream.write(buffer, 0, read); - } - - return new String(outputStream.toByteArray(), StandardCharsets.UTF_8); - } - - private SampleUtils() { - } -} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/TrustSelfSignedServerDelegate.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/TrustSelfSignedServerDelegate.java deleted file mode 100644 index 0103ce37b5e3..000000000000 --- a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/TrustSelfSignedServerDelegate.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -package com.azure.security.keyvault.jca; - -import javax.net.ssl.X509TrustManager; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Objects; - -/** - * Implementation of {@link X509TrustManager} that wraps another {@link X509TrustManager} with a check where self-signed - * server chains are trusted. - *

- * This implementation uses basic validation for checking if the chain is self-signed, where it only checks that the - * chain has a length of one. This validation only applies when running - * {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)}, and if it passes that method call does not - * delegate to the wrapped {@link X509TrustManager}. - *

- * {@link X509TrustManager#checkClientTrusted(X509Certificate[], String)} and - * {@link X509TrustManager#getAcceptedIssuers()} always delegate to the wrapped {@link X509TrustManager}. - */ -public final class TrustSelfSignedServerDelegate implements X509TrustManager { - private final X509TrustManager delegate; - - /** - * Creates a new instance of {@link TrustSelfSignedServerDelegate}. - * - * @param delegate The {@link X509TrustManager} that this {@link TrustSelfSignedServerDelegate} will delegate. - */ - public TrustSelfSignedServerDelegate(X509TrustManager delegate) { - this.delegate = Objects.requireNonNull(delegate, "'delegate' cannot be null."); - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - delegate.checkClientTrusted(chain, authType); - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - if (chain.length != 1) { - checkServerTrusted(chain, authType); - } - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return delegate.getAcceptedIssuers(); - } -} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ClientMTLSSample.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ClientMTLSSample.java index b3f3711b4a6f..d3003ed55b8a 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ClientMTLSSample.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ClientMTLSSample.java @@ -4,16 +4,25 @@ import com.azure.security.keyvault.jca.KeyVaultJcaProvider; import com.azure.security.keyvault.jca.KeyVaultKeyStore; -import com.azure.security.keyvault.jca.SampleUtils; import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; import java.net.URI; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.Security; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * The ClientMTLS sample. @@ -23,53 +32,114 @@ public class ClientMTLSSample { public static void main(String[] args) throws Exception { // BEGIN: readme-sample-clientMTLS KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); + // Register the provider before requesting its KeyStore implementation. Security.addProvider(provider); System.setProperty("azure.keyvault.uri", ""); System.setProperty("azure.keyvault.tenant-id", ""); System.setProperty("azure.keyvault.client-id", ""); System.setProperty("azure.keyvault.client-secret", ""); + // Load the certificate and private key that identify this client to the server. KeyStore keyStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); System.setProperty("azure.keyvault.uri", ""); System.setProperty("azure.keyvault.tenant-id", ""); System.setProperty("azure.keyvault.client-id", ""); System.setProperty("azure.keyvault.client-secret", ""); + // Load the server certificates that this client trusts. KeyStore trustStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); - // This section initializing SSLContext can be replaced with implementation specific consumption of 'KeyStore', - // if the library being used has convenience methods for that. + // Create trust managers from the server trust material. + TrustManagerFactory trustManagerFactory + = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + + // The local server may use a self-signed certificate. Accept a one-certificate server chain while delegating + // validation of all other chains to the platform trust manager. Do not use this behavior in production. + for (int i = 0; i < trustManagers.length; i++) { + if (trustManagers[i] instanceof X509TrustManager) { + X509TrustManager delegate = (X509TrustManager) trustManagers[i]; + trustManagers[i] = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + delegate.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + if (chain.length != 1) { + delegate.checkServerTrusted(chain, authType); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return delegate.getAcceptedIssuers(); + } + }; + } + } + + // Create key managers that select the client certificate and private key during the mTLS handshake. + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, "".toCharArray()); + + // Combine the client identity with the server trust configuration. SSLContext sslContext = SSLContext.getInstance("TLS"); - TrustManager[] trustManagers = SampleUtils.loadTrustMaterial(keyStore); - KeyManager[] keyManagers = SampleUtils.loadKeyMaterial(keyStore, "".toCharArray()); - sslContext.init(keyManagers, trustManagers, null); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagers, null); String result = null; HttpsURLConnection connection = null; + try { // openConnection will return HttpsURLConnection when the protocol is 'https'. connection = (HttpsURLConnection) URI.create("https://localhost:8765").toURL().openConnection(); - // Have the HttpsURLConnection use the SSLSocketFactory returned by SSLContext. + // Apply the custom identity and trust configuration to this HTTPS connection. connection.setSSLSocketFactory(sslContext.getSocketFactory()); + // Allow the sample certificate to use a hostname other than localhost. Do not do this in production. + connection.setHostnameVerifier((hostname, session) -> true); connection.setRequestMethod("GET"); int status = connection.getResponseCode(); + if (status == 200) { - result = SampleUtils.readResponse(connection); + // Decode the response using its declared charset, or UTF-8 when no charset is present. + Charset responseCharset = StandardCharsets.UTF_8; + String contentType = connection.getContentType(); + if (contentType != null) { + Matcher matcher = Pattern.compile("(?i)\\bcharset\\s*=\\s*\"?([^;\\s\"]+)") + .matcher(contentType); + if (matcher.find()) { + responseCharset = Charset.forName(matcher.group(1)); + } + } + + // Read the complete body without changing its line endings. + try (Reader reader = new InputStreamReader(connection.getInputStream(), responseCharset)) { + StringBuilder responseBody = new StringBuilder(); + char[] buffer = new char[1024]; + int read; + while ((read = reader.read(buffer)) != -1) { + responseBody.append(buffer, 0, read); + } + result = responseBody.toString(); + } } else { result = "Not success"; } } catch (IOException ioe) { ioe.printStackTrace(); - result = "Not success"; } finally { if (connection != null) { connection.disconnect(); } } + System.out.println(result); // END: readme-sample-clientMTLS } - } diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ServerMTLSSample.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ServerMTLSSample.java index 3f83b02ed56d..574d322522ec 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ServerMTLSSample.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/mtls/ServerMTLSSample.java @@ -25,14 +25,17 @@ public class ServerMTLSSample { public static void main(String[] args) throws Exception { // BEGIN: readme-sample-serverMTLS KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); + // Register the provider before requesting its KeyStore implementation. Security.addProvider(provider); System.setProperty("azure.keyvault.uri", ""); System.setProperty("azure.keyvault.tenant-id", ""); System.setProperty("azure.keyvault.client-id", ""); System.setProperty("azure.keyvault.client-secret", ""); + // Load the certificate and private key that identify this server to connecting clients. KeyStore keyStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); + // Key managers select the server certificate and private key during each mTLS handshake. KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, "".toCharArray()); @@ -40,24 +43,30 @@ public static void main(String[] args) throws Exception { System.setProperty("azure.keyvault.tenant-id", ""); System.setProperty("azure.keyvault.client-id", ""); System.setProperty("azure.keyvault.client-secret", ""); + // Load the client certificates that this server trusts. KeyStore trustStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); + // Trust managers validate the certificate presented by each client. TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); + // Combine the server identity with the client trust configuration. SSLContext context = SSLContext.getInstance("TLS"); context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); SSLServerSocketFactory socketFactory = context.getServerSocketFactory(); SSLServerSocket serverSocket = (SSLServerSocket) socketFactory.createServerSocket(8765); + // Require every client to present a trusted certificate during the TLS handshake. serverSocket.setNeedClientAuth(true); while (true) { + // Accept an mTLS connection and write a minimal HTTP response over it. SSLSocket socket = (SSLSocket) serverSocket.accept(); System.out.println("Client connected: " + socket.getInetAddress()); BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String body = "Hello, this is server."; + // Build a minimal HTTP response and calculate Content-Length from the UTF-8 body bytes. String response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: " + body.getBytes(StandardCharsets.UTF_8).length + "\r\nConnection: close\r\n\r\n" + body; diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ClientSSLSample.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ClientSSLSample.java index cf7d49a6f202..1094d3f1f256 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ClientSSLSample.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ClientSSLSample.java @@ -4,15 +4,24 @@ import com.azure.security.keyvault.jca.KeyVaultJcaProvider; import com.azure.security.keyvault.jca.KeyVaultKeyStore; -import com.azure.security.keyvault.jca.SampleUtils; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; import java.net.URI; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.Security; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * The ClientSSL sample. @@ -27,14 +36,47 @@ public static void main(String[] args) throws Exception { System.setProperty("azure.keyvault.client-secret", ""); KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); + // Register the provider before requesting its KeyStore implementation. Security.addProvider(provider); KeyStore keyStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); - // This section initializing SSLContext can be replaced with implementation specific consumption of 'KeyStore', - // if the library being used has convenience methods for that. + // Create trust managers from the certificates in the Key Vault-backed KeyStore. + TrustManagerFactory trustManagerFactory + = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + + // The local server may use a self-signed certificate. Accept a one-certificate server chain while delegating + // validation of all other chains to the platform trust manager. Do not use this behavior in production. + for (int i = 0; i < trustManagers.length; i++) { + if (trustManagers[i] instanceof X509TrustManager) { + X509TrustManager delegate = (X509TrustManager) trustManagers[i]; + trustManagers[i] = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + delegate.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + if (chain.length != 1) { + delegate.checkServerTrusted(chain, authType); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return delegate.getAcceptedIssuers(); + } + }; + } + } + + // Configure one-way TLS: the client validates the server but doesn't present a client certificate. SSLContext sslContext = SSLContext.getInstance("TLS"); - TrustManager[] trustManagers = SampleUtils.loadTrustMaterial(keyStore); sslContext.init(null, trustManagers, null); String result = null; @@ -43,19 +85,40 @@ public static void main(String[] args) throws Exception { // openConnection will return HttpsURLConnection when the protocol is 'https'. connection = (HttpsURLConnection) URI.create("https://localhost:8765").toURL().openConnection(); - // Have the HttpsURLConnection use the SSLSocketFactory returned by SSLContext. + // Apply the custom trust configuration to this HTTPS connection. connection.setSSLSocketFactory(sslContext.getSocketFactory()); + // Allow the sample certificate to use a hostname other than localhost. Do not do this in production. + connection.setHostnameVerifier((hostname, session) -> true); connection.setRequestMethod("GET"); int status = connection.getResponseCode(); if (status == 200) { - result = SampleUtils.readResponse(connection); + // Decode the response using its declared charset, or UTF-8 when no charset is present. + Charset responseCharset = StandardCharsets.UTF_8; + String contentType = connection.getContentType(); + if (contentType != null) { + Matcher matcher = Pattern.compile("(?i)\\bcharset\\s*=\\s*\"?([^;\\s\"]+)") + .matcher(contentType); + if (matcher.find()) { + responseCharset = Charset.forName(matcher.group(1)); + } + } + + // Read the complete body without changing its line endings. + try (Reader reader = new InputStreamReader(connection.getInputStream(), responseCharset)) { + StringBuilder responseBody = new StringBuilder(); + char[] buffer = new char[1024]; + int read; + while ((read = reader.read(buffer)) != -1) { + responseBody.append(buffer, 0, read); + } + result = responseBody.toString(); + } } else { result = "Not success"; } } catch (IOException ioe) { ioe.printStackTrace(); - result = "Not success"; } finally { if (connection != null) { connection.disconnect(); diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ServerSSLSample.java b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ServerSSLSample.java index 491c8113c4ae..614bee61948d 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ServerSSLSample.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/samples/java/com/azure/security/keyvault/jca/tls/ServerSSLSample.java @@ -29,13 +29,17 @@ public static void main(String[] args) throws Exception { System.setProperty("azure.keyvault.client-secret", ""); KeyVaultJcaProvider provider = new KeyVaultJcaProvider(); + // Register the provider before requesting its KeyStore implementation. Security.addProvider(provider); + // Load the certificate and private key that identify this server to connecting clients. KeyStore keyStore = KeyVaultKeyStore.getKeyVaultKeyStoreBySystemProperty(); + // Key managers select the server certificate and private key during each TLS handshake. KeyManagerFactory managerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); managerFactory.init(keyStore, "".toCharArray()); + // Configure one-way TLS: clients aren't required to present a certificate. SSLContext context = SSLContext.getInstance("TLS"); context.init(managerFactory.getKeyManagers(), null, null); @@ -43,11 +47,13 @@ public static void main(String[] args) throws Exception { SSLServerSocket serverSocket = (SSLServerSocket) socketFactory.createServerSocket(8765); while (true) { + // Accept a TLS connection and write a minimal HTTP response over it. SSLSocket socket = (SSLSocket) serverSocket.accept(); System.out.println("Client connected: " + socket.getInetAddress()); BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String body = "Hello, this is server."; + // Build a minimal HTTP response and calculate Content-Length from the UTF-8 body bytes. String response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: " + body.getBytes(StandardCharsets.UTF_8).length + "\r\nConnection: close\r\n\r\n" + body; From 5d3a43dd319660bb3d7fe5b813a5b140335ffc5c Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Tue, 1 Sep 2026 09:54:29 -0700 Subject: [PATCH 14/16] Fix JCA HTTP migration tests --- .../keyvault/jca/JreKeyStoreTest.java | 1 + .../keyvault/jca/ServerSocketTest.java | 1 + .../implementation/KeyVaultClientTest.java | 48 +++++-------------- 3 files changed, 14 insertions(+), 36 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java index fcca34eb1a77..76b056c7f93b 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java @@ -66,6 +66,7 @@ public void testJreKsTrustPeer() throws Exception { HttpsURLConnection connection = null; try { connection = (HttpsURLConnection) URI.create("https://google.com:443").toURL().openConnection(); + connection.setSSLSocketFactory(sslContext.getSocketFactory()); connection.setRequestMethod("GET"); if (connection.getResponseCode() == 200) { result = "Success"; diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java index a83d91e3cb87..6e2c63efcca7 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/ServerSocketTest.java @@ -182,6 +182,7 @@ private String sendRequest(SSLContext sslContext, int port) { try { connection = (HttpsURLConnection) URI.create("https://localhost:" + port).toURL().openConnection(); connection.setSSLSocketFactory(sslContext.getSocketFactory()); + connection.setHostnameVerifier((hostname, session) -> true); connection.setRequestMethod("GET"); if (connection.getResponseCode() == 204) { result = "Success"; diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java index aa8407ddd963..7b302732d0df 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/KeyVaultClientTest.java @@ -50,6 +50,8 @@ public class KeyVaultClientTest { private static final String KEY_VAULT_TEST_URI_GLOBAL = "https://fake.vault.azure.net/"; + private static final String TEST_ACCESS_TOKEN = "test-token"; + private static final String CERTIFICATE_ALIAS = "client-cert"; private static final String CERTIFICATE_URI @@ -62,12 +64,7 @@ public class KeyVaultClientTest { @Test public void testGetAliasWithCertificateInfoWith0Page() { - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { - @Override - String httpGet(String uri, Map headers) { - return "fakeValue"; - } - }; + KeyVaultClient keyVaultClient = new TestKeyVaultClient(TEST_ACCESS_TOKEN, false, (uri, headers) -> "fakeValue"); assertEquals(0, keyVaultClient.getAliases().size()); } @@ -83,12 +80,8 @@ public void testGetAliasWithCertificateInfoWith1Page() { String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { - @Override - String httpGet(String uri, Map headers) { - return certificateListResultString; - } - }; + KeyVaultClient keyVaultClient + = new TestKeyVaultClient(TEST_ACCESS_TOKEN, false, (uri, headers) -> certificateListResultString); List result = keyVaultClient.getAliases(); assertEquals(1, result.size()); @@ -119,12 +112,8 @@ public void testGetAliasWithCertificateInfoWith2Pages() { String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); String certificateListResultStringNext = JsonConverterUtil.toJson(certificateListResultNext); - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { - @Override - String httpGet(String uri, Map headers) { - return "fakeNextLink".equals(uri) ? certificateListResultStringNext : certificateListResultString; - } - }; + KeyVaultClient keyVaultClient = new TestKeyVaultClient(TEST_ACCESS_TOKEN, false, (uri, + headers) -> "fakeNextLink".equals(uri) ? certificateListResultStringNext : certificateListResultString); List result = keyVaultClient.getAliases(); assertEquals(3, result.size()); @@ -153,12 +142,8 @@ public void testGetAliasFiltersOutDisabledCertificate() { String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { - @Override - String httpGet(String uri, Map headers) { - return certificateListResultString; - } - }; + KeyVaultClient keyVaultClient + = new TestKeyVaultClient(TEST_ACCESS_TOKEN, false, (uri, headers) -> certificateListResultString); List result = keyVaultClient.getAliases(); assertEquals(1, result.size()); @@ -184,12 +169,8 @@ public void testGetAliasKeepsEnabledAndAttributelessCertificates() { String certificateListResultString = JsonConverterUtil.toJson(certificateListResult); - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { - @Override - String httpGet(String uri, Map headers) { - return certificateListResultString; - } - }; + KeyVaultClient keyVaultClient + = new TestKeyVaultClient(TEST_ACCESS_TOKEN, false, (uri, headers) -> certificateListResultString); List result = keyVaultClient.getAliases(); assertEquals(2, result.size()); @@ -206,12 +187,7 @@ public void testGetAliasFiltersDisabledCertificateFromRawResponse() { + "{\"id\":\"https://fake.vault.azure.net/certificates/client-cert-unused\"," + "\"attributes\":{\"enabled\":false,\"nbf\":1783324860,\"exp\":1814861460}}]," + "\"nextLink\":null}"; - KeyVaultClient keyVaultClient = new KeyVaultClient(KEY_VAULT_TEST_URI_GLOBAL, null) { - @Override - String httpGet(String uri, Map headers) { - return rawResponse; - } - }; + KeyVaultClient keyVaultClient = new TestKeyVaultClient(TEST_ACCESS_TOKEN, false, (uri, headers) -> rawResponse); List result = keyVaultClient.getAliases(); assertEquals(1, result.size()); From 9abdb606931cd3d739b25f85aad4e778c106fbc3 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Tue, 1 Sep 2026 09:56:08 -0700 Subject: [PATCH 15/16] Clean up JCA HTTP migration dependencies --- sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md | 1 + sdk/keyvault/azure-security-keyvault-jca/pom.xml | 11 ----------- .../implementation/utils/AiaCertificateChainTest.java | 6 ++++-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md index 366ac061f8d7..f6d584c06a75 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md +++ b/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md @@ -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) diff --git a/sdk/keyvault/azure-security-keyvault-jca/pom.xml b/sdk/keyvault/azure-security-keyvault-jca/pom.xml index 6605164f7a88..493645c99a3a 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/pom.xml +++ b/sdk/keyvault/azure-security-keyvault-jca/pom.xml @@ -48,12 +48,6 @@ 1.5.1 true - - - org.slf4j - slf4j-nop - 1.7.36 - com.azure azure-core @@ -183,10 +177,6 @@ com.azure.json com.azure.security.keyvault.jca.implementation.shaded.com.azure.json - - org.slf4j - com.azure.security.keyvault.jca.implementation.shaded.org.slf4j - @@ -227,7 +217,6 @@ org.bouncycastle:bcpkix-lts8on:[2.73.11] org.conscrypt:conscrypt-openjdk-uber:[2.5.2] - org.slf4j:slf4j-nop:[1.7.36] diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainTest.java index 207b684061ec..00c7ee3ce031 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainTest.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.api.parallel.Isolated; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -80,9 +81,10 @@ * only its leaf certificate in the secret bundle. The missing intermediate CA certificates * must be downloaded via the CA Issuers URL in the AIA extension of each certificate. * - *

Tests must run sequentially because they share JVM-global state (system properties, the AIA response cache, - * and the AIA response loader). Parallel execution would cause property-pollution flakiness. + *

Tests must run in isolation because they share JVM-global state (system properties, the AIA response cache, + * and the AIA response loader). Running another test class concurrently could make it use this class's response loader. */ +@Isolated("Mutates the global AIA response loader and cache") @Execution(ExecutionMode.SAME_THREAD) public class AiaCertificateChainTest { From 21b5b610fe23bbc09a107d4fc1c81f145988cdf5 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Tue, 1 Sep 2026 11:52:34 -0700 Subject: [PATCH 16/16] Harden JCA HTTP error handling --- .../implementation/utils/AccessTokenUtil.java | 23 +++++++++-- .../jca/implementation/utils/HttpUtil.java | 39 ++++++++----------- .../utils/AccessTokenUtilTest.java | 11 ++++++ .../implementation/utils/HttpUtilTest.java | 22 ++++++++++- 4 files changed, 69 insertions(+), 26 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java index cbf7802488e8..74acc9e3e4fb 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java @@ -179,6 +179,16 @@ interface HttpPoster { String post(String uri, Map headers, String body, String contentType); } + /** + * Functional interface for retrieving HTTP response headers. + * + *

Introduced to be used for testing purposes, allowing the HTTP response headers to be mocked or overridden. + */ + @FunctionalInterface + interface HttpHeaderRetriever { + Map> get(String uri); + } + /** * Check if Azure Workload Identity environment is available. * @@ -442,18 +452,25 @@ private static AccessToken getAccessTokenOnOthers(String resource, String identi } public static String getLoginUri(String resourceUri, boolean disableChallengeResourceVerification) { + return getLoginUri(resourceUri, disableChallengeResourceVerification, HttpUtil::getWithOnlyResponseHeaders); + } + + // Overloaded method that allows specifying a custom HttpHeaderRetriever for testing. + static String getLoginUri(String resourceUri, boolean disableChallengeResourceVerification, + HttpHeaderRetriever httpHeaderRetriever) { LOGGER.entering("AccessTokenUtil", "getLoginUri", resourceUri); LOGGER.log(INFO, "Getting login URI using: {0}", resourceUri); - Map> headers = HttpUtil.getWithOnlyResponseHeaders(resourceUri); + Map> headers = httpHeaderRetriever.get(resourceUri); if (headers == null) { throw new IllegalStateException("Could not obtain login URI to retrieve access token from."); } List wwwAuthenticates = headers.get(WWW_AUTHENTICATE); - Map challengeAttributes - = extractChallengeAttributes(wwwAuthenticates == null ? null : wwwAuthenticates.get(0)); + String wwwAuthenticate + = wwwAuthenticates == null || wwwAuthenticates.isEmpty() ? null : wwwAuthenticates.get(0); + Map challengeAttributes = extractChallengeAttributes(wwwAuthenticate); String scope = challengeAttributes.get("resource"); if (scope != null) { 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 2e0c0dd15b54..456f30611cb5 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 @@ -13,7 +13,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.UncheckedIOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; @@ -221,7 +220,7 @@ static BinaryHttpResponse getAiaBytesWithMetadata(String url, ConnectionFactory return new BinaryHttpResponse(readResponseBytes(connection.getInputStream(), currentUrl), cacheControl, date, age, expires); - } catch (IOException | IllegalArgumentException | ClassCastException | UncheckedIOException e) { + } catch (IOException | IllegalArgumentException | ClassCastException e) { // Catch all exceptions including IOException, IllegalArgumentException, and other runtime exceptions // that may occur during HTTP execution. Gracefully return null to allow AIA completion to fail silently // the entire jarsigner/signing operation. @@ -384,8 +383,8 @@ public static String getUserAgentPrefix() { } private static String createErrorMessage(int status) { - return "Failed to get response from Key Vault because return http status code is " + status + ". It can be " - + "caused by missing permissions or roles. To know how to add permissions or roles, see " + return "Failed to get a response from Key Vault because the HTTP status code was " + status + ". This may be " + + "caused by missing permissions or roles. For instructions on assigning permissions or roles, see " + "https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/keyvault/azure-security-keyvault-jca#prerequisites."; } @@ -474,30 +473,26 @@ private static void configureConnection(HttpURLConnection connection, String met connection.setRequestProperty(USER_AGENT_KEY, USER_AGENT_VALUE); } - private static HttpURLConnection openConnection(String uri) { - try { - HttpURLConnection connection = (HttpURLConnection) URI.create(uri).toURL().openConnection(); + private static HttpURLConnection openConnection(String uri) throws IOException { + HttpURLConnection connection = (HttpURLConnection) URI.create(uri).toURL().openConnection(); - if (connection instanceof HttpsURLConnection) { - try { - TrustManagerFactory trustManagerFactory - = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + if (connection instanceof HttpsURLConnection) { + try { + TrustManagerFactory trustManagerFactory + = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(JreKeyStoreFactory.getDefaultKeyStore()); + trustManagerFactory.init(JreKeyStoreFactory.getDefaultKeyStore()); - SSLContext sslContext = SSLContext.getInstance("TLS"); + SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, trustManagerFactory.getTrustManagers(), null); - ((HttpsURLConnection) connection).setSSLSocketFactory(sslContext.getSocketFactory()); - } catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) { - LOGGER.log(WARNING, "Unable to build the SSL context.", e); - } + sslContext.init(null, trustManagerFactory.getTrustManagers(), null); + ((HttpsURLConnection) connection).setSSLSocketFactory(sslContext.getSocketFactory()); + } catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) { + LOGGER.log(WARNING, "Unable to build the SSL context.", e); } - - return connection; - } catch (IOException ex) { - throw new UncheckedIOException(ex); } + + return connection; } 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/AccessTokenUtilTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtilTest.java index 41a63a0dfd22..baace3c5f8f7 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtilTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtilTest.java @@ -10,7 +10,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -83,4 +85,13 @@ public void close() { assertFalse(loggedValues.contains(clientSecret), "The client secret must never be logged"); assertTrue(loggedValues.contains("client-id"), "Non-secret parameters stay available for diagnostics"); } + + @Test + void getLoginUriReturnsNullForEmptyAuthenticateHeader() { + Map> headers = Collections.singletonMap("WWW-Authenticate", Collections.emptyList()); + + String result = AccessTokenUtil.getLoginUri("https://vault.azure.net", false, ignored -> headers); + + assertNull(result); + } } 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 23ff93a7bc2b..61ae85fdbc1b 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 @@ -8,6 +8,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; @@ -82,7 +83,7 @@ void textGetThrowsForNonSuccessfulResponse() throws Exception { RuntimeException exception = assertThrows(RuntimeException.class, () -> HttpUtil.get("https://example.test/value", null, ignored -> connection)); - assertTrue(exception.getMessage().contains("429")); + assertTrue(exception.getMessage().contains("HTTP status code was 429")); assertTrue(connection.disconnected); } @@ -146,6 +147,25 @@ void binaryResponsePreservesBodyAndFreshnessHeaders() throws Exception { assertTrue(connection.disconnected); } + @Test + void connectionOpeningFailuresReturnNull() { + String unsupportedUrl = "unsupported://example.test"; + + assertNull(HttpUtil.get(unsupportedUrl, null)); + assertNull(HttpUtil.post(unsupportedUrl, null, "request", "application/json")); + assertNull(HttpUtil.getWithOnlyResponseHeaders(unsupportedUrl)); + } + + @Test + void binaryResponseHandlesConnectionOpeningFailure() { + HttpUtil.BinaryHttpResponse result + = HttpUtil.getAiaBytesWithMetadata("https://example.test/cert.crt", ignored -> { + throw new IOException("Connection failed"); + }); + + assertNull(result.getBody()); + } + @Test void binaryResponseForFailureHasNoBodyAndPreservesFreshnessMetadata() throws Exception { Map> headers