Skip to content

Commit a468e67

Browse files
authored
Build the SSL context with jre key store to back up the KeyVaultKeyStore'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
1 parent 2028a28 commit a468e67

File tree

6 files changed

+168
-73
lines changed

6 files changed

+168
-73
lines changed

eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@
167167
<!-- Suppress Redundant nullcheck error for JreCertificates$JREKeyStore.loadKeyStore(KeyStore). -->
168168
<Match>
169169
<Or>
170-
<Class name="com.azure.security.keyvault.jca.implementation.certificates.JreCertificates$JREKeyStore"/> <!-- false positive -->
170+
<Class name="com.azure.security.keyvault.jca.implementation.JREKeyStoreFactory"/> <!-- false positive -->
171171
</Or>
172172
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
173173
</Match>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package com.azure.security.keyvault.jca.implementation;
4+
5+
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
import java.nio.file.Paths;
11+
import java.security.AccessController;
12+
import java.security.KeyStore;
13+
import java.security.KeyStoreException;
14+
import java.security.NoSuchAlgorithmException;
15+
import java.security.PrivilegedAction;
16+
import java.security.cert.CertificateException;
17+
import java.util.Objects;
18+
import java.util.Optional;
19+
import java.util.logging.Logger;
20+
import java.util.stream.Stream;
21+
22+
import static java.util.logging.Level.WARNING;
23+
24+
/**
25+
* This class provides a JRE key store.
26+
*/
27+
public final class JREKeyStoreFactory {
28+
private static final String JAVA_HOME = privilegedGetProperty("java.home", "");
29+
private static final Path STORE_PATH = Paths.get(JAVA_HOME).resolve("lib").resolve("security");
30+
private static final Path DEFAULT_STORE = STORE_PATH.resolve("cacerts");
31+
private static final Path JSSE_DEFAULT_STORE = STORE_PATH.resolve("jssecacerts");
32+
private static final String KEY_STORE_PASSWORD = privilegedGetProperty("javax.net.ssl.keyStorePassword", "changeit");
33+
private static final Logger LOGGER = Logger.getLogger(JREKeyStoreFactory.class.getName());
34+
private static final KeyStore JRE_KEY_STORE = getJreKeyStore();
35+
36+
37+
private JREKeyStoreFactory() {
38+
39+
}
40+
41+
/**
42+
* This method returns the instance of JRE key store
43+
* @return the JRE key store.
44+
*/
45+
public static KeyStore getDefaultKeyStore() {
46+
return JRE_KEY_STORE;
47+
}
48+
49+
50+
private static KeyStore getJreKeyStore() {
51+
KeyStore defaultKeyStore = null;
52+
try {
53+
defaultKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
54+
loadKeyStore(defaultKeyStore);
55+
} catch (KeyStoreException e) {
56+
LOGGER.log(WARNING, "Unable to get the jre key store.", e);
57+
}
58+
return defaultKeyStore;
59+
}
60+
61+
private static void loadKeyStore(KeyStore ks) {
62+
try (InputStream inputStream = Files.newInputStream(getKeyStoreFile())) {
63+
ks.load(inputStream, KEY_STORE_PASSWORD.toCharArray());
64+
} catch (IOException | NoSuchAlgorithmException | CertificateException e) {
65+
LOGGER.log(WARNING, "unable to load the jre key store", e);
66+
}
67+
}
68+
69+
private static Path getKeyStoreFile() {
70+
return Stream.of(getConfiguredKeyStorePath(), JSSE_DEFAULT_STORE, DEFAULT_STORE)
71+
.filter(Objects::nonNull)
72+
.filter(Files::exists)
73+
.filter(Files::isReadable)
74+
.findFirst()
75+
.orElse(null);
76+
}
77+
78+
private static Path getConfiguredKeyStorePath() {
79+
String configuredKeyStorePath = privilegedGetProperty("javax.net.ssl.keyStore", "");
80+
return Optional.of(configuredKeyStorePath)
81+
.filter(path -> !path.isEmpty())
82+
.map(Paths::get)
83+
.orElse(null);
84+
}
85+
86+
private static String privilegedGetProperty(String theProp, String defaultVal) {
87+
return AccessController.doPrivileged(
88+
(PrivilegedAction<String>) () -> {
89+
String value = System.getProperty(theProp, "");
90+
return (value.isEmpty()) ? defaultVal : value;
91+
});
92+
}
93+
}

sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/certificates/JreCertificates.java

Lines changed: 4 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,17 @@
33

44
package com.azure.security.keyvault.jca.implementation.certificates;
55

6-
import java.io.IOException;
7-
import java.io.InputStream;
8-
import java.nio.file.Files;
9-
import java.nio.file.Path;
10-
import java.nio.file.Paths;
11-
import java.security.AccessController;
6+
import com.azure.security.keyvault.jca.implementation.JREKeyStoreFactory;
127
import java.security.Key;
138
import java.security.KeyStore;
149
import java.security.KeyStoreException;
15-
import java.security.NoSuchAlgorithmException;
16-
import java.security.PrivilegedAction;
1710
import java.security.cert.Certificate;
18-
import java.security.cert.CertificateException;
19-
import java.util.stream.Stream;
2011
import java.util.Collections;
2112
import java.util.List;
2213
import java.util.Map;
2314
import java.util.Optional;
2415
import java.util.HashMap;
2516
import java.util.logging.Logger;
26-
import java.util.Objects;
27-
2817
import static java.util.logging.Level.WARNING;
2918

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

5443
/**
55-
* Stores the singleton
44+
* Stores the instance of JreCertificates.
5645
*/
5746
private static final JreCertificates INSTANCE = new JreCertificates();
5847

5948
/**
6049
* Private constructor
6150
*/
6251
private JreCertificates() {
63-
KeyStore jreKeyStore = JREKeyStore.getDefault();
52+
KeyStore jreKeyStore = JREKeyStoreFactory.getDefaultKeyStore();
6453
aliases = Optional.ofNullable(jreKeyStore)
6554
.map(a -> {
6655
try {
@@ -87,7 +76,7 @@ private JreCertificates() {
8776

8877
/**
8978
*
90-
* @return the singleton.
79+
* @return the instance of JreCertificates.
9180
*/
9281
public static JreCertificates getInstance() {
9382
return INSTANCE;
@@ -113,56 +102,4 @@ public Map<String, Key> getCertificateKeys() {
113102
public void deleteEntry(String alias) {
114103

115104
}
116-
117-
private static class JREKeyStore {
118-
private static final String JAVA_HOME = privilegedGetProperty("java.home", "");
119-
private static final Path STORE_PATH = Paths.get(JAVA_HOME).resolve("lib").resolve("security");
120-
private static final Path DEFAULT_STORE = STORE_PATH.resolve("cacerts");
121-
private static final Path JSSE_DEFAULT_STORE = STORE_PATH.resolve("jssecacerts");
122-
private static final String KEY_STORE_PASSWORD = privilegedGetProperty("javax.net.ssl.keyStorePassword", "changeit");
123-
124-
private static KeyStore getDefault() {
125-
KeyStore defaultKeyStore = null;
126-
try {
127-
defaultKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
128-
loadKeyStore(defaultKeyStore);
129-
} catch (KeyStoreException e) {
130-
LOGGER.log(WARNING, "Unable to get the jre key store.", e);
131-
}
132-
return defaultKeyStore;
133-
}
134-
135-
private static void loadKeyStore(KeyStore ks) {
136-
try (InputStream inputStream = Files.newInputStream(getKeyStoreFile())) {
137-
ks.load(inputStream, KEY_STORE_PASSWORD.toCharArray());
138-
} catch (IOException | NoSuchAlgorithmException | CertificateException e) {
139-
LOGGER.log(WARNING, "unable to load the jre key store", e);
140-
}
141-
}
142-
143-
private static Path getKeyStoreFile() {
144-
return Stream.of(getConfiguredKeyStorePath(), JSSE_DEFAULT_STORE, DEFAULT_STORE)
145-
.filter(Objects::nonNull)
146-
.filter(Files::exists)
147-
.filter(Files::isReadable)
148-
.findFirst()
149-
.orElse(null);
150-
}
151-
152-
private static Path getConfiguredKeyStorePath() {
153-
String configuredKeyStorePath = privilegedGetProperty("javax.net.ssl.keyStore", "");
154-
return Optional.of(configuredKeyStorePath)
155-
.filter(path -> !path.isEmpty())
156-
.map(Paths::get)
157-
.orElse(null);
158-
}
159-
160-
private static String privilegedGetProperty(String theProp, String defaultVal) {
161-
return AccessController.doPrivileged(
162-
(PrivilegedAction<String>) () -> {
163-
String value = System.getProperty(theProp, "");
164-
return (value.isEmpty()) ? defaultVal : value;
165-
});
166-
}
167-
}
168105
}

sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtil.java

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,39 @@
22
// Licensed under the MIT License.
33
package com.azure.security.keyvault.jca.implementation.utils;
44

5+
import com.azure.security.keyvault.jca.implementation.JREKeyStoreFactory;
56
import org.apache.http.HttpEntity;
67
import org.apache.http.HttpResponse;
78
import org.apache.http.client.ResponseHandler;
89
import org.apache.http.client.methods.HttpGet;
910
import org.apache.http.client.methods.HttpPost;
11+
import org.apache.http.config.RegistryBuilder;
12+
import org.apache.http.conn.socket.ConnectionSocketFactory;
13+
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
1014
import org.apache.http.entity.ContentType;
1115
import org.apache.http.entity.StringEntity;
1216
import org.apache.http.impl.client.CloseableHttpClient;
1317
import org.apache.http.impl.client.HttpClients;
18+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
19+
import org.apache.http.ssl.SSLContexts;
1420
import org.apache.http.util.EntityUtils;
1521

22+
import javax.net.ssl.HostnameVerifier;
23+
import javax.net.ssl.SSLContext;
1624
import java.io.BufferedReader;
1725
import java.io.IOException;
1826
import java.io.InputStreamReader;
27+
import java.security.KeyManagementException;
28+
import java.security.KeyStore;
29+
import java.security.KeyStoreException;
30+
import java.security.NoSuchAlgorithmException;
1931
import java.util.Map;
2032
import java.util.Optional;
33+
import java.util.logging.Logger;
2134
import java.util.stream.Stream;
2235

36+
import static java.util.logging.Level.WARNING;
37+
2338
/**
2439
* The RestClient that uses the Apache HttpClient class.
2540
*/
@@ -33,18 +48,19 @@ public final class HttpUtil {
3348
.map(Package::getImplementationVersion)
3449
.orElse(DEFAULT_VERSION);
3550
public static final String USER_AGENT_VALUE = getUserAgentPrefix() + VERSION;
51+
private static final Logger LOGGER = Logger.getLogger(HttpUtil.class.getName());
3652

3753
public static String get(String url, Map<String, String> headers) {
3854
String result = null;
39-
try (CloseableHttpClient client = HttpClients.createDefault()) {
55+
try (CloseableHttpClient client = buildClient()) {
4056
HttpGet httpGet = new HttpGet(url);
4157
if (headers != null) {
4258
headers.forEach(httpGet::addHeader);
4359
}
4460
httpGet.addHeader(USER_AGENT_KEY, USER_AGENT_VALUE);
4561
result = client.execute(httpGet, createResponseHandler());
4662
} catch (IOException ioe) {
47-
ioe.printStackTrace();
63+
LOGGER.log(WARNING, "Unable to finish the http get request.", ioe);
4864
}
4965
return result;
5066
}
@@ -67,7 +83,7 @@ public static String getUserAgentPrefix() {
6783

6884
public static String post(String url, Map<String, String> headers, String body, String contentType) {
6985
String result = null;
70-
try (CloseableHttpClient client = HttpClients.createDefault()) {
86+
try (CloseableHttpClient client = buildClient()) {
7187
HttpPost httpPost = new HttpPost(url);
7288
httpPost.addHeader(USER_AGENT_KEY, USER_AGENT_VALUE);
7389
if (headers != null) {
@@ -77,7 +93,7 @@ public static String post(String url, Map<String, String> headers, String body,
7793
httpPost.setEntity(new StringEntity(body, ContentType.create(contentType)));
7894
result = client.execute(httpPost, createResponseHandler());
7995
} catch (IOException ioe) {
80-
ioe.printStackTrace();
96+
LOGGER.log(WARNING, "Unable to finish the http post request.", ioe);
8197
}
8298
return result;
8399
}
@@ -94,4 +110,27 @@ private static ResponseHandler<String> createResponseHandler() {
94110
return result;
95111
};
96112
}
113+
114+
private static CloseableHttpClient buildClient() {
115+
KeyStore keyStore = JREKeyStoreFactory.getDefaultKeyStore();
116+
117+
SSLContext sslContext = null;
118+
try {
119+
sslContext = SSLContexts
120+
.custom()
121+
.loadTrustMaterial(keyStore, null)
122+
.build();
123+
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
124+
LOGGER.log(WARNING, "Unable to build the ssl context.", e);
125+
}
126+
127+
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
128+
sslContext, (HostnameVerifier) null);
129+
130+
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(
131+
RegistryBuilder.<ConnectionSocketFactory>create()
132+
.register("https", sslConnectionSocketFactory)
133+
.build());
134+
return HttpClients.custom().setConnectionManager(manager).build();
135+
}
97136
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package com.azure.security.keyvault.jca.implementation;
4+
5+
import com.azure.security.keyvault.jca.KeyVaultKeyStore;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.security.KeyStore;
9+
10+
import static org.junit.jupiter.api.Assertions.assertFalse;
11+
12+
public class JREKeyStoreFactoryTest {
13+
@Test
14+
public void test() {
15+
KeyStore jreKeyStore = JREKeyStoreFactory.getDefaultKeyStore();
16+
assertFalse(jreKeyStore.getType().equals(KeyVaultKeyStore.KEY_STORE_TYPE));
17+
}
18+
}

sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import static com.azure.security.keyvault.jca.implementation.utils.HttpUtil.DEFAULT_USER_AGENT_VALUE_PREFIX;
99
import static com.azure.security.keyvault.jca.implementation.utils.HttpUtil.VERSION;
10-
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.*;
1111

1212
public class HttpUtilTest {
1313

@@ -16,4 +16,12 @@ public void getUserAgentPrefixTest() {
1616
assertEquals(DEFAULT_USER_AGENT_VALUE_PREFIX, HttpUtil.getUserAgentPrefix());
1717
assertEquals(DEFAULT_USER_AGENT_VALUE_PREFIX + VERSION, HttpUtil.USER_AGENT_VALUE);
1818
}
19+
20+
@Test
21+
public void testCustomizedHttpsClient() {
22+
String url = "https://google.com";
23+
String result = HttpUtil.get(url, null);
24+
assertNotNull(result);
25+
assertFalse(result.isEmpty());
26+
}
1927
}

0 commit comments

Comments
 (0)