Skip to content

Commit

Permalink
Build the SSL context with jre key store to back up the KeyVaultKeySt…
Browse files Browse the repository at this point in the history
…ore's own functions. (Azure#23923)

* Build the SSL context with jre key store

* Add exemption of spotbugs

* Added test for cutomized https client

Co-authored-by: michaelqi793
  • Loading branch information
michaelqi793 authored Sep 13, 2021
1 parent 2028a28 commit a468e67
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
<!-- Suppress Redundant nullcheck error for JreCertificates$JREKeyStore.loadKeyStore(KeyStore). -->
<Match>
<Or>
<Class name="com.azure.security.keyvault.jca.implementation.certificates.JreCertificates$JREKeyStore"/> <!-- false positive -->
<Class name="com.azure.security.keyvault.jca.implementation.JREKeyStoreFactory"/> <!-- false positive -->
</Or>
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
</Match>
Expand Down
Original file line number Diff line number Diff line change
@@ -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>) () -> {
String value = System.getProperty(theProp, "");
return (value.isEmpty()) ? defaultVal : value;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -52,15 +41,15 @@ public final class JreCertificates implements AzureCertificates {
private final Map<String, Key> keys;

/**
* Stores the singleton
* Stores the instance of JreCertificates.
*/
private static final JreCertificates INSTANCE = new JreCertificates();

/**
* Private constructor
*/
private JreCertificates() {
KeyStore jreKeyStore = JREKeyStore.getDefault();
KeyStore jreKeyStore = JREKeyStoreFactory.getDefaultKeyStore();
aliases = Optional.ofNullable(jreKeyStore)
.map(a -> {
try {
Expand All @@ -87,7 +76,7 @@ private JreCertificates() {

/**
*
* @return the singleton.
* @return the instance of JreCertificates.
*/
public static JreCertificates getInstance() {
return INSTANCE;
Expand All @@ -113,56 +102,4 @@ public Map<String, Key> 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>) () -> {
String value = System.getProperty(theProp, "");
return (value.isEmpty()) ? defaultVal : value;
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -33,18 +48,19 @@ 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<String, String> 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);
}
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;
}
Expand All @@ -67,7 +83,7 @@ public static String getUserAgentPrefix() {

public static String post(String url, Map<String, String> 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) {
Expand All @@ -77,7 +93,7 @@ public static String post(String url, Map<String, String> 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;
}
Expand All @@ -94,4 +110,27 @@ private static ResponseHandler<String> 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.<ConnectionSocketFactory>create()
.register("https", sslConnectionSocketFactory)
.build());
return HttpClients.custom().setConnectionManager(manager).build();
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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());
}
}

0 comments on commit a468e67

Please sign in to comment.