Skip to content

Commit

Permalink
Add jre key store certificates to key vault key store, and add test f…
Browse files Browse the repository at this point in the history
…or it. (#21845)
  • Loading branch information
michaelqi793 authored May 31, 2021
1 parent 387e742 commit a2209fd
Show file tree
Hide file tree
Showing 4 changed files with 311 additions and 8 deletions.
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,168 @@
// 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 java.util.Objects;

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 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() {
KeyStore 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;
})
.orElseGet(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);
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);
}
}

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 @@ -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;

/**
* 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();
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")
);
}

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

0 comments on commit a2209fd

Please sign in to comment.