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 3 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
@@ -0,0 +1,167 @@
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.KeyStore;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.security.AccessController;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import static java.util.logging.Level.WARNING;

public class JreCertificates implements AzureCertificates {
/**
* Stores the logger.
*/
private static final Logger LOGGER = Logger.getLogger(JreCertificates.class.getName());

/**
* Stores the jre key store.
*/
private static final KeyStore JRE_KEY_STORE;

/**
* Stores the jre key store aliases.
*/
private static final List<String> JRE_KS_ALIASES;
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Stores the jre key store certificates.
*/
private static final Map<String, Certificate> JRE_KS_CERTS;
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved

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

static {
JRE_KEY_STORE = JREKeyStore.getDefault();
List<String> JRE_KS_ALIASES1 = Collections.emptyList();
if (null != JRE_KEY_STORE) {
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
try {
JRE_KS_ALIASES1 = Collections.unmodifiableList(Collections.list(JRE_KEY_STORE.aliases()));
} catch (KeyStoreException e) {
LOGGER.log(WARNING, "Unable to load the jre key store aliases.", e);
}
}
JRE_KS_ALIASES = JRE_KS_ALIASES1;
JRE_KS_CERTS = JRE_KS_ALIASES.stream()
.collect(Collectors.toMap(a -> a, a -> {
try {
return JRE_KEY_STORE.getCertificate(a);
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
} catch (KeyStoreException e) {
LOGGER.log(WARNING, "Unable to get the jre key store certificate.", e);
}
return null;
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved
}));
}

private JreCertificates() {}

public static JreCertificates getInstance() {
return INSTANCE;
}


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

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

@Override
public Map<String, Key> getCertificateKeys() {
throw new UnsupportedOperationException();
}

@Override
public void deleteEntry(String alias) {
throw new UnsupportedOperationException();

}

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) {
InputStream inStream = null;
try {
inStream = Files.newInputStream(getKeyStoreFile());
ks.load(inStream, KEY_STORE_PASSWORD.toCharArray());
} catch (IOException | NoSuchAlgorithmException | CertificateException e) {
LOGGER.log(WARNING, "unable to load the jre key store", e);
} finally {
try {
inStream.close();
} catch (NullPointerException | IOException e ) {
LOGGER.log(WARNING, "", e);
}
}
}
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved

private static Path getKeyStoreFile() {
String storePropName = privilegedGetProperty(
"javax.net.ssl.keyStore", "");
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved
return getStoreFile(storePropName);
}

private static Path getStoreFile(String storePropName) {
Path storeProp;
if (storePropName.isEmpty()) {
storeProp = JSSE_DEFAULT_STORE;
} else {
storeProp = Paths.get(storePropName);
}

Path[] fileNames = new Path[]{storeProp, DEFAULT_STORE};
return Arrays.stream(fileNames)
.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) {
if (System.getSecurityManager() == null) {
String value = System.getProperty(theProp, "");
return (value.isEmpty()) ? defaultVal : value;
} else {
return AccessController.doPrivileged(
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved
(PrivilegedAction<String>) () -> {
String value = System.getProperty(theProp, "");
return (value.isEmpty()) ? defaultVal : value;
});
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
*/
private final KeyVaultCertificates keyVaultCertificates;

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

/**
* Stores the creation date.
*/
Expand Down Expand Up @@ -111,11 +116,12 @@ 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
}

@Override
public Enumeration<String> engineAliases() {
List<String> aliasList = Stream.of(keyVaultCertificates, classpathCertificates)
List<String> aliasList = Stream.of(keyVaultCertificates, classpathCertificates, jreCertificates)
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved
.map(AzureCertificates::getAliases)
.flatMap(Collection::stream)
.distinct().collect(Collectors.toList());
Expand All @@ -141,7 +147,7 @@ public boolean engineEntryInstanceOf(String alias, Class<? extends KeyStore.Entr

@Override
public Certificate engineGetCertificate(String alias) {
Certificate certificate = Stream.of(keyVaultCertificates, classpathCertificates)
Certificate certificate = Stream.of(keyVaultCertificates, classpathCertificates, jreCertificates)
.map(AzureCertificates::getCertificates)
.filter(a -> a.containsKey(alias))
.findFirst()
Expand All @@ -159,7 +165,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 = Stream.of(keyVaultCertificates, classpathCertificates, jreCertificates)
.map(AzureCertificates::getAliases)
.flatMap(Collection::stream)
.distinct()
Expand Down Expand Up @@ -202,6 +208,9 @@ public KeyStore.Entry engineGetEntry(String alias, KeyStore.ProtectionParameter

@Override
public Key engineGetKey(String alias, char[] password) {
/**
* Jre key store is deliberately unused here.
*/
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved
return Stream.of(keyVaultCertificates, classpathCertificates)
.map(AzureCertificates::getCertificateKeys)
.filter(a -> a.containsKey(alias))
Expand All @@ -212,7 +221,7 @@ public Key engineGetKey(String alias, char[] password) {

@Override
public boolean engineIsCertificateEntry(String alias) {
return Stream.of(keyVaultCertificates, classpathCertificates)
return Stream.of(keyVaultCertificates, classpathCertificates, jreCertificates)
.map(AzureCertificates::getAliases)
.flatMap(Collection::stream)
.distinct()
Expand All @@ -221,7 +230,14 @@ public boolean engineIsCertificateEntry(String alias) {

@Override
public boolean engineIsKeyEntry(String alias) {
return engineIsCertificateEntry(alias);
/**
* Jre key store is deliberately unused here.
michaelqi793 marked this conversation as resolved.
Show resolved Hide resolved
*/
return Stream.of(keyVaultCertificates, classpathCertificates)
.map(AzureCertificates::getAliases)
.flatMap(Collection::stream)
.distinct()
.anyMatch(a -> Objects.equals(a, alias));
}

@Override
Expand Down Expand Up @@ -284,7 +300,7 @@ public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) {

@Override
public int engineSize() {
return Stream.of(keyVaultCertificates, classpathCertificates)
return Stream.of(keyVaultCertificates, classpathCertificates, jreCertificates)
.map(AzureCertificates::getAliases)
.flatMap(Collection::stream)
.distinct()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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.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.util.Arrays;

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

@EnabledIfEnvironmentVariable(named = "AZURE_KEYVAULT_CERTIFICATE_NAME", matches = "myalias")
public class JreKeyStoreTest {
@Test
public void testJreKS() throws Exception {
/*
* Add JCA provider.
*/
KeyVaultJcaProvider provider = new KeyVaultJcaProvider();
Security.addProvider(provider);
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
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);
}
}