Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add jre key store certificates to key vault key store, and add test for it. #21845

Merged
merged 32 commits into from
May 31, 2021
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0c152c6
Add jre key store certificates to key vault keystore
May 26, 2021
eb31e52
Merge remote-tracking branch into local_jre_with_refresh
May 26, 2021
bb5c756
minor code change
May 26, 2021
4001d9b
fix checkstyle failures
May 26, 2021
b3aa1e7
Minor code change
May 26, 2021
885ab8f
Fix checkstyle failures and some test
May 27, 2021
e45e507
Merge remote-tracking branch into local_jre_with_refresh
May 27, 2021
59449a5
minor code change
May 27, 2021
53a5c3b
minor code change
May 27, 2021
746d0e5
Minor text change
May 27, 2021
81627a1
Merge remote-tracking branch into local_jre_with_refresh
May 27, 2021
29cf190
Minor text change
May 27, 2021
24ad907
Change static fields to be instance fields
May 27, 2021
dbaef66
Merge remote-tracking branch into local_jre_with_refresh
May 27, 2021
ce1853b
Fix checkstyle error
May 27, 2021
2835aec
Merge remote-tracking branch into local_jre_with_refresh
May 27, 2021
9097c26
Fix checkstyle failure
May 27, 2021
09abf23
Merge remote-tracking branch into local_jre_with_refresh
May 27, 2021
1da9298
Use try-with-resource instead of close inputStream manually.
rujche May 28, 2021
ab5daae
remove thread sleep
May 28, 2021
93c3917
remove thread sleep
May 28, 2021
c21201d
remove return null in Collectors.toMap
May 28, 2021
0dd1415
fix checkstyle failures
May 28, 2021
6042ddd
revert non-related test change
May 28, 2021
1830b0a
add exemption for redundant non-null check for JreCertificates
May 28, 2021
4118c85
Minor code change
May 28, 2021
3b924ae
Merge remote-tracking branch into local_jre_with_refresh
May 28, 2021
bd9a2da
Minor code change
May 31, 2021
f177a3a
Merge remote-tracking branch into local_jre_with_refresh
May 31, 2021
2896a1b
Merge remote-tracking branch into local_jre_with_refresh
May 31, 2021
86c9c24
Merge remote-tracking branch into local_jre_with_refresh
May 31, 2021
65b9e91
re-organize code
May 31, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
</Match>

<!-- Suppress Redundant nullcheck error for JreCertificates$JREKeyStore.loadKeyStore(KeyStore). -->
<Match>
<Or>
<Class name="com.azure.security.keyvault.jca.JreCertificates$JREKeyStore"/> <!-- false positive -->
</Or>
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
</Match>


<Match>
<Or>
<Class name="com.azure.core.util.CoreUtils"/> <!-- false positive -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.security.keyvault.jca;

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.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 static java.util.logging.Level.WARNING;

/**
* This class provides the certificates from jre key store. It only provides certificates. It does not provide key entries from jre key store.
*/
public final class JreCertificates implements AzureCertificates {
/**
* Stores the logger.
*/
private static final Logger LOGGER = Logger.getLogger(JreCertificates.class.getName());

/**
* Stores the jre key store.
*/
private final KeyStore jreKeyStore;
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Stores the jre key store aliases.
*/
private final List<String> aliases;

/**
* Stores the jre key store certificates.
*/
private final Map<String, Certificate> certs;

/**
* Stores the jre key store keys
*/
private final Map<String, Key> keys;

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

/**
* Private constructor
*/
private JreCertificates() {
jreKeyStore = JREKeyStore.getDefault();
aliases = Optional.ofNullable(jreKeyStore).map(a -> {
try {
return Collections.unmodifiableList(Collections.list(a.aliases()));
} catch (KeyStoreException e) {
LOGGER.log(WARNING, "Unable to load the jre key store aliases.", e);
}
return null; // It's ok to return null here. Null will be handled by Optional.
}).orElse(Collections.emptyList());
certs = aliases.stream()
.collect(HashMap::new, (m, v) -> {
try {
m.put(v, jreKeyStore.getCertificate(v));
} catch (KeyStoreException e) {
LOGGER.log(WARNING, "Unable to get the jre key store certificate.", e);
}
}, HashMap::putAll);
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved
keys = Collections.emptyMap();
}

/**
*
* @return the singleton.
*/
public static JreCertificates getInstance() {
return INSTANCE;
}


@Override
public List<String> getAliases() {
return aliases;
}

@Override
public Map<String, Certificate> getCertificates() {
return certs;
}

@Override
public Map<String, Key> getCertificateKeys() {
return keys;
}

@Override
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);
}
}
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved

private static Path getKeyStoreFile() {
String storePropName = privilegedGetProperty("javax.net.ssl.keyStore", "");
return getStoreFile(storePropName);
}

private static Path getStoreFile(String storePropName) {
Path storeProp;
if (storePropName.isEmpty()) {
storeProp = JSSE_DEFAULT_STORE;
} else {
storeProp = Paths.get(storePropName);
}
return Stream.of(storeProp, DEFAULT_STORE)
.filter(a -> Files.exists(a) && Files.isReadable(a))
.findFirst().orElse(null);
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved
}
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -28,9 +28,9 @@
import java.util.List;
import java.util.Collection;
import java.util.Optional;
import java.util.Arrays;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
Expand Down Expand Up @@ -65,6 +65,16 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
*/
private final KeyVaultCertificates keyVaultCertificates;

/**
* Stores the Jre key store certificates.
*/
private final JreCertificates jreCertificates;

/**
* Stores all the certificates.
*/
private final List<AzureCertificates> allCertificates;
chenrujun marked this conversation as resolved.
Show resolved Hide resolved

/**
* Stores the creation date.
*/
Expand Down Expand Up @@ -111,14 +121,17 @@ public KeyVaultKeyStore() {
.orElse(false);
keyVaultCertificates = new KeyVaultCertificates(refreshInterval, keyVaultClient);
classpathCertificates = new ClasspathCertificates();
jreCertificates = JreCertificates.getInstance();
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved
allCertificates = Arrays.asList(keyVaultCertificates, classpathCertificates, jreCertificates);
}

@Override
public Enumeration<String> engineAliases() {
List<String> aliasList = Stream.of(keyVaultCertificates, classpathCertificates)
List<String> aliasList = allCertificates.stream()
.map(AzureCertificates::getAliases)
.flatMap(Collection::stream)
.distinct().collect(Collectors.toList());
.distinct()
.collect(Collectors.toList());

return Collections.enumeration(aliasList);
}
Expand All @@ -141,7 +154,7 @@ public boolean engineEntryInstanceOf(String alias, Class<? extends KeyStore.Entr

@Override
public Certificate engineGetCertificate(String alias) {
Certificate certificate = Stream.of(keyVaultCertificates, classpathCertificates)
Certificate certificate = allCertificates.stream()
.map(AzureCertificates::getCertificates)
.filter(a -> a.containsKey(alias))
.findFirst()
Expand All @@ -159,7 +172,7 @@ public Certificate engineGetCertificate(String alias) {
public String engineGetCertificateAlias(Certificate cert) {
String alias = null;
if (cert != null) {
List<String> aliasList = Stream.of(keyVaultCertificates, classpathCertificates)
List<String> aliasList = allCertificates.stream()
.map(AzureCertificates::getAliases)
.flatMap(Collection::stream)
.distinct()
Expand Down Expand Up @@ -202,7 +215,7 @@ public KeyStore.Entry engineGetEntry(String alias, KeyStore.ProtectionParameter

@Override
public Key engineGetKey(String alias, char[] password) {
return Stream.of(keyVaultCertificates, classpathCertificates)
return allCertificates.stream()
.map(AzureCertificates::getCertificateKeys)
.filter(a -> a.containsKey(alias))
.findFirst()
Expand All @@ -212,7 +225,7 @@ public Key engineGetKey(String alias, char[] password) {

@Override
public boolean engineIsCertificateEntry(String alias) {
return Stream.of(keyVaultCertificates, classpathCertificates)
return allCertificates.stream()
.map(AzureCertificates::getAliases)
.flatMap(Collection::stream)
.distinct()
Expand Down Expand Up @@ -284,7 +297,7 @@ public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) {

@Override
public int engineSize() {
return Stream.of(keyVaultCertificates, classpathCertificates)
return allCertificates.stream()
.map(AzureCertificates::getAliases)
.flatMap(Collection::stream)
.distinct()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.security.keyvault.jca;

import org.apache.http.HttpResponse;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
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.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

@EnabledIfEnvironmentVariable(named = "AZURE_KEYVAULT_CERTIFICATE_NAME", matches = "myalias")
public class JreKeyStoreTest {
@BeforeAll
public static void init() {
/*
* Add JCA provider.
*/
KeyVaultJcaProvider provider = new KeyVaultJcaProvider();
Security.addProvider(provider);
/*
* Set system properties.
*/

PropertyConvertorUtils.putEnvironmentPropertyToSystemProperty(
Arrays.asList("AZURE_KEYVAULT_URI",
"AZURE_KEYVAULT_TENANT_ID",
"AZURE_KEYVAULT_CLIENT_ID",
"AZURE_KEYVAULT_CLIENT_SECRET")
);
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
public void testJreKsEntries() {
JreCertificates jreCertificates = JreCertificates.getInstance();
assertNotNull(jreCertificates);
assertNotNull(jreCertificates.getAliases());
Map<String, Certificate> certs = jreCertificates.getCertificates();
assertTrue(certs.containsKey("globalsignr2ca [jdk]"));
assertNotNull(certs.get("globalsignr2ca [jdk]"));
assertNotNull(jreCertificates.getCertificateKeys());
}

@Test
public void testJreKsTrustPeer() throws Exception {

KeyStore ks = KeyStore.getInstance("AzureKeyVault");
ks.load(null);
/*
* Setup client side
*
* - Create an SSL context.
* - 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.<ConnectionSocketFactory>create()
.register("https", sslConnectionSocketFactory)
.build());

/*
* And now execute the test.
*/
String result = null;

try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).build()) {
HttpGet httpGet = new HttpGet("https://google.com:443");
ResponseHandler<String> responseHandler = (HttpResponse response) -> {
int status = response.getStatusLine().getStatusCode();
String result1 = null;
if (status == 200) {
result1 = "Success";
}
return result1;
};
result = client.execute(httpGet, responseHandler);
} catch (IOException ioe) {
ioe.printStackTrace();
}

/*
* And verify all went well.
*/
assertEquals("Success", result);
}
}