From a468e673c5a6bad9e38e4e70c832a16382b3b366 Mon Sep 17 00:00:00 2001 From: michaelqi793 <78671298+michaelqi793@users.noreply.github.com> Date: Mon, 13 Sep 2021 17:43:25 +0800 Subject: [PATCH] Build the SSL context with jre key store to back up the KeyVaultKeyStore's own functions. (#23923) * Build the SSL context with jre key store * Add exemption of spotbugs * Added test for cutomized https client Co-authored-by: michaelqi793 --- .../resources/spotbugs/spotbugs-exclude.xml | 2 +- .../implementation/JREKeyStoreFactory.java | 93 +++++++++++++++++++ .../certificates/JreCertificates.java | 71 +------------- .../jca/implementation/utils/HttpUtil.java | 47 +++++++++- .../JREKeyStoreFactoryTest.java | 18 ++++ .../implementation/utils/HttpUtilTest.java | 10 +- 6 files changed, 168 insertions(+), 73 deletions(-) create mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/JREKeyStoreFactory.java create mode 100644 sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/JREKeyStoreFactoryTest.java diff --git a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml index b07da304ba766..67e2613f1df90 100755 --- a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml +++ b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml @@ -167,7 +167,7 @@ - + diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/JREKeyStoreFactory.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/JREKeyStoreFactory.java new file mode 100644 index 0000000000000..3d99fb5143dce --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/JREKeyStoreFactory.java @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.security.keyvault.jca.implementation; + + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.AccessController; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivilegedAction; +import java.security.cert.CertificateException; +import java.util.Objects; +import java.util.Optional; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import static java.util.logging.Level.WARNING; + +/** + * This class provides a JRE key store. + */ +public final class JREKeyStoreFactory { + private static final String JAVA_HOME = privilegedGetProperty("java.home", ""); + private static final Path STORE_PATH = Paths.get(JAVA_HOME).resolve("lib").resolve("security"); + private static final Path DEFAULT_STORE = STORE_PATH.resolve("cacerts"); + private static final Path JSSE_DEFAULT_STORE = STORE_PATH.resolve("jssecacerts"); + private static final String KEY_STORE_PASSWORD = privilegedGetProperty("javax.net.ssl.keyStorePassword", "changeit"); + private static final Logger LOGGER = Logger.getLogger(JREKeyStoreFactory.class.getName()); + private static final KeyStore JRE_KEY_STORE = getJreKeyStore(); + + + private JREKeyStoreFactory() { + + } + + /** + * This method returns the instance of JRE key store + * @return the JRE key store. + */ + public static KeyStore getDefaultKeyStore() { + return JRE_KEY_STORE; + } + + + private static KeyStore getJreKeyStore() { + KeyStore defaultKeyStore = null; + try { + defaultKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + loadKeyStore(defaultKeyStore); + } catch (KeyStoreException e) { + LOGGER.log(WARNING, "Unable to get the jre key store.", e); + } + return defaultKeyStore; + } + + private static void loadKeyStore(KeyStore ks) { + try (InputStream inputStream = Files.newInputStream(getKeyStoreFile())) { + ks.load(inputStream, KEY_STORE_PASSWORD.toCharArray()); + } catch (IOException | NoSuchAlgorithmException | CertificateException e) { + LOGGER.log(WARNING, "unable to load the jre key store", e); + } + } + + private static Path getKeyStoreFile() { + return Stream.of(getConfiguredKeyStorePath(), JSSE_DEFAULT_STORE, DEFAULT_STORE) + .filter(Objects::nonNull) + .filter(Files::exists) + .filter(Files::isReadable) + .findFirst() + .orElse(null); + } + + private static Path getConfiguredKeyStorePath() { + String configuredKeyStorePath = privilegedGetProperty("javax.net.ssl.keyStore", ""); + return Optional.of(configuredKeyStorePath) + .filter(path -> !path.isEmpty()) + .map(Paths::get) + .orElse(null); + } + + private static String privilegedGetProperty(String theProp, String defaultVal) { + return AccessController.doPrivileged( + (PrivilegedAction) () -> { + String value = System.getProperty(theProp, ""); + return (value.isEmpty()) ? defaultVal : value; + }); + } +} diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/JreCertificates.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/JreCertificates.java index b5ececb6d4a6b..240491300d2e2 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/JreCertificates.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/JreCertificates.java @@ -3,28 +3,17 @@ package com.azure.security.keyvault.jca.implementation.certificates; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.AccessController; +import com.azure.security.keyvault.jca.implementation.JREKeyStoreFactory; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivilegedAction; import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.util.stream.Stream; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.HashMap; import java.util.logging.Logger; -import java.util.Objects; - import static java.util.logging.Level.WARNING; /** @@ -52,7 +41,7 @@ public final class JreCertificates implements AzureCertificates { private final Map keys; /** - * Stores the singleton + * Stores the instance of JreCertificates. */ private static final JreCertificates INSTANCE = new JreCertificates(); @@ -60,7 +49,7 @@ public final class JreCertificates implements AzureCertificates { * Private constructor */ private JreCertificates() { - KeyStore jreKeyStore = JREKeyStore.getDefault(); + KeyStore jreKeyStore = JREKeyStoreFactory.getDefaultKeyStore(); aliases = Optional.ofNullable(jreKeyStore) .map(a -> { try { @@ -87,7 +76,7 @@ private JreCertificates() { /** * - * @return the singleton. + * @return the instance of JreCertificates. */ public static JreCertificates getInstance() { return INSTANCE; @@ -113,56 +102,4 @@ public Map getCertificateKeys() { public void deleteEntry(String alias) { } - - private static class JREKeyStore { - private static final String JAVA_HOME = privilegedGetProperty("java.home", ""); - private static final Path STORE_PATH = Paths.get(JAVA_HOME).resolve("lib").resolve("security"); - private static final Path DEFAULT_STORE = STORE_PATH.resolve("cacerts"); - private static final Path JSSE_DEFAULT_STORE = STORE_PATH.resolve("jssecacerts"); - private static final String KEY_STORE_PASSWORD = privilegedGetProperty("javax.net.ssl.keyStorePassword", "changeit"); - - private static KeyStore getDefault() { - KeyStore defaultKeyStore = null; - try { - defaultKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - loadKeyStore(defaultKeyStore); - } catch (KeyStoreException e) { - LOGGER.log(WARNING, "Unable to get the jre key store.", e); - } - return defaultKeyStore; - } - - private static void loadKeyStore(KeyStore ks) { - try (InputStream inputStream = Files.newInputStream(getKeyStoreFile())) { - ks.load(inputStream, KEY_STORE_PASSWORD.toCharArray()); - } catch (IOException | NoSuchAlgorithmException | CertificateException e) { - LOGGER.log(WARNING, "unable to load the jre key store", e); - } - } - - private static Path getKeyStoreFile() { - return Stream.of(getConfiguredKeyStorePath(), JSSE_DEFAULT_STORE, DEFAULT_STORE) - .filter(Objects::nonNull) - .filter(Files::exists) - .filter(Files::isReadable) - .findFirst() - .orElse(null); - } - - private static Path getConfiguredKeyStorePath() { - String configuredKeyStorePath = privilegedGetProperty("javax.net.ssl.keyStore", ""); - return Optional.of(configuredKeyStorePath) - .filter(path -> !path.isEmpty()) - .map(Paths::get) - .orElse(null); - } - - private static String privilegedGetProperty(String theProp, String defaultVal) { - return AccessController.doPrivileged( - (PrivilegedAction) () -> { - String value = System.getProperty(theProp, ""); - return (value.isEmpty()) ? defaultVal : value; - }); - } - } } 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 a53faeed4e980..2972c7b3e876b 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 @@ -2,24 +2,39 @@ // Licensed under the MIT License. package com.azure.security.keyvault.jca.implementation.utils; +import com.azure.security.keyvault.jca.implementation.JREKeyStoreFactory; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.Optional; +import java.util.logging.Logger; import java.util.stream.Stream; +import static java.util.logging.Level.WARNING; + /** * The RestClient that uses the Apache HttpClient class. */ @@ -33,10 +48,11 @@ public final class HttpUtil { .map(Package::getImplementationVersion) .orElse(DEFAULT_VERSION); public static final String USER_AGENT_VALUE = getUserAgentPrefix() + VERSION; + private static final Logger LOGGER = Logger.getLogger(HttpUtil.class.getName()); public static String get(String url, Map headers) { String result = null; - try (CloseableHttpClient client = HttpClients.createDefault()) { + try (CloseableHttpClient client = buildClient()) { HttpGet httpGet = new HttpGet(url); if (headers != null) { headers.forEach(httpGet::addHeader); @@ -44,7 +60,7 @@ public static String get(String url, Map headers) { httpGet.addHeader(USER_AGENT_KEY, USER_AGENT_VALUE); result = client.execute(httpGet, createResponseHandler()); } catch (IOException ioe) { - ioe.printStackTrace(); + LOGGER.log(WARNING, "Unable to finish the http get request.", ioe); } return result; } @@ -67,7 +83,7 @@ public static String getUserAgentPrefix() { public static String post(String url, Map headers, String body, String contentType) { String result = null; - try (CloseableHttpClient client = HttpClients.createDefault()) { + try (CloseableHttpClient client = buildClient()) { HttpPost httpPost = new HttpPost(url); httpPost.addHeader(USER_AGENT_KEY, USER_AGENT_VALUE); if (headers != null) { @@ -77,7 +93,7 @@ public static String post(String url, Map headers, String body, httpPost.setEntity(new StringEntity(body, ContentType.create(contentType))); result = client.execute(httpPost, createResponseHandler()); } catch (IOException ioe) { - ioe.printStackTrace(); + LOGGER.log(WARNING, "Unable to finish the http post request.", ioe); } return result; } @@ -94,4 +110,27 @@ private static ResponseHandler createResponseHandler() { return result; }; } + + private static CloseableHttpClient buildClient() { + KeyStore keyStore = JREKeyStoreFactory.getDefaultKeyStore(); + + SSLContext sslContext = null; + 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("https", sslConnectionSocketFactory) + .build()); + return HttpClients.custom().setConnectionManager(manager).build(); + } } 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 new file mode 100644 index 0000000000000..2461d1a5f7122 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/JREKeyStoreFactoryTest.java @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.security.keyvault.jca.implementation; + +import com.azure.security.keyvault.jca.KeyVaultKeyStore; +import org.junit.jupiter.api.Test; + +import java.security.KeyStore; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class JREKeyStoreFactoryTest { + @Test + public void test() { + KeyStore jreKeyStore = JREKeyStoreFactory.getDefaultKeyStore(); + assertFalse(jreKeyStore.getType().equals(KeyVaultKeyStore.KEY_STORE_TYPE)); + } +} 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 3b7a1f273aecc..faaf32670e239 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 @@ -7,7 +7,7 @@ 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.assertEquals; +import static org.junit.jupiter.api.Assertions.*; public class HttpUtilTest { @@ -16,4 +16,12 @@ public void getUserAgentPrefixTest() { assertEquals(DEFAULT_USER_AGENT_VALUE_PREFIX, HttpUtil.getUserAgentPrefix()); assertEquals(DEFAULT_USER_AGENT_VALUE_PREFIX + VERSION, HttpUtil.USER_AGENT_VALUE); } + + @Test + public void testCustomizedHttpsClient() { + String url = "https://google.com"; + String result = HttpUtil.get(url, null); + assertNotNull(result); + assertFalse(result.isEmpty()); + } }