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 to key vault key store #21293

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
09694a2
add jre key store
May 10, 2021
aacc942
Merge remote-tracking branch into dev_ks_jreks
May 10, 2021
f3e2f65
minor change
May 10, 2021
e05eb8d
minor change
May 10, 2021
0dfaf7e
Merge remote-tracking branch into dev_ks_jreks
May 11, 2021
dcc4995
add jre key store
May 11, 2021
9bd7ff9
import clauses change
May 11, 2021
0814b93
import clauses change
May 11, 2021
b07b85b
import clauses change
May 11, 2021
3f87f18
format change
May 11, 2021
4c175ab
format change
May 11, 2021
2182d5f
format change
May 11, 2021
cdeab01
format change
May 12, 2021
1da84ef
remove redundant non-null check
May 13, 2021
f3afdae
minor revert
May 13, 2021
216a124
fix checkstyle
May 14, 2021
4352e8a
Merge remote-tracking branch into dev_ks_jreks
May 17, 2021
5ed0921
check alias before getting cert.
May 17, 2021
6f7210f
add EnabledIfEnvironmentVariable
May 17, 2021
4ebe896
Merge remote-tracking branch into dev_ks_jreks
May 17, 2021
5ae8733
rearrange code because pipleline spotbugs complains about redundant n…
May 18, 2021
91dda2a
Merge remote-tracking branch into dev_ks_jreks
May 18, 2021
6cfc5ab
Merge remote-tracking branch into dev_ks_jreks
May 18, 2021
ec512e1
Merge remote-tracking branch into dev_ks_jreks
May 19, 2021
0952d86
Merge remote-tracking branch into dev_ks_jreks
May 20, 2021
dc0e596
Merge remote-tracking branch into dev_ks_jreks
May 21, 2021
76632b1
Merge remote-tracking branch into dev_ks_jreks
May 21, 2021
b2e29fd
Merge remote-tracking branch into dev_ks_jreks
May 25, 2021
aba3530
Fix check style failures
May 25, 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 @@ -10,12 +10,18 @@
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.PrivilegedAction;
import java.security.AccessController;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
Expand All @@ -26,8 +32,9 @@
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.logging.Logger;

import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;

Expand Down Expand Up @@ -76,13 +83,23 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
*/
private KeyVaultClient keyVaultClient;

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

/**
* Stores the jre key store aliases.
*/
private static final Set<String> JRE_ALIASES;

/**
* Constructor.
*
* <p>
* The constructor uses System.getProperty for
* <code>azure.keyvault.uri</code>,
* <code>azure.keyvault.aadAuthenticationUrl</code>,
* <code>azure.keyvault.uri</code>,
* <code>azure.keyvault.aadAuthenticationUrl</code>,
* <code>azure.keyvault.tenantId</code>,
* <code>azure.keyvault.clientId</code>,
* <code>azure.keyvault.clientSecret</code> and
Expand All @@ -102,14 +119,30 @@ public KeyVaultKeyStore() {
} else {
keyVaultClient = new KeyVaultClient(keyVaultUri, managedIdentity);
}

}

static {
DEFAULT_KEY_STORE = JREKeyStore.getDefault();
JRE_ALIASES = new HashSet<>();
if (null != DEFAULT_KEY_STORE) {
try {
JRE_ALIASES.addAll(Collections.list(DEFAULT_KEY_STORE.aliases()));
} catch (KeyStoreException e) {
LOGGER.log(WARNING, "Unable to load the jre key store aliases.", e);
}
}
}

@Override
public Enumeration<String> engineAliases() {
if (aliases == null) {
aliases = keyVaultClient.getAliases();
}
return Collections.enumeration(aliases);
Set<String> als = new HashSet<>();
als.addAll(aliases);
als.addAll(JRE_ALIASES);
return Collections.enumeration(als);
}

@Override
Expand All @@ -128,18 +161,24 @@ public boolean engineEntryInstanceOf(String alias, Class<? extends KeyStore.Entr

@Override
public Certificate engineGetCertificate(String alias) {
Certificate certificate;
Certificate certificate = null;
if (certificates.containsKey(alias)) {
certificate = certificates.get(alias);
} else {
certificate = keyVaultClient.getCertificate(alias);
if (certificate != null) {
certificates.put(alias, certificate);
if (aliases == null) {
aliases = keyVaultClient.getAliases();
if (aliases == null) {
aliases = keyVaultClient.getAliases();
}
if (aliases.contains(alias)) {
certificate = keyVaultClient.getCertificate(alias);
if (certificate != null) {
certificates.put(alias, certificate);
}
if (!aliases.contains(alias)) {
aliases.add(alias);
}
if (certificate == null) {
try {
certificate = DEFAULT_KEY_STORE.getCertificate(alias);
} catch (KeyStoreException e) {
LOGGER.log(WARNING, "Unable to load certificate from jre Key store.", e);
}
}
}
Expand All @@ -160,6 +199,13 @@ public String engineGetCertificateAlias(Certificate cert) {
break;
}
}
if (null == alias) {
try {
alias = DEFAULT_KEY_STORE.getCertificateAlias(cert);
} catch (KeyStoreException e) {
LOGGER.log(WARNING, "Unable to load the alias from jre Key store.", e);
}
}
}
return alias;
}
Expand All @@ -171,6 +217,12 @@ public Certificate[] engineGetCertificateChain(String alias) {
if (certificate != null) {
chain = new Certificate[1];
chain[0] = certificate;
} else {
try {
chain = DEFAULT_KEY_STORE.getCertificateChain(alias);
} catch (KeyStoreException e) {
LOGGER.log(WARNING, "Unable to load the certificate chain from jre Key store.", e);
}
}
return chain;
}
Expand Down Expand Up @@ -200,6 +252,8 @@ public Key engineGetKey(String alias, char[] password) {
if (!aliases.contains(alias)) {
aliases.add(alias);
}
} else {
key = JREKeyStore.getKey(DEFAULT_KEY_STORE, alias);
}
}
return key;
Expand All @@ -210,12 +264,18 @@ public boolean engineIsCertificateEntry(String alias) {
if (aliases == null) {
aliases = keyVaultClient.getAliases();
}
return aliases.contains(alias);
Set<String> als = new HashSet<>();
als.addAll(aliases);
als.addAll(JRE_ALIASES);
return als.contains(alias);
}

@Override
public boolean engineIsKeyEntry(String alias) {
return engineIsCertificateEntry(alias);
if (aliases == null) {
aliases = keyVaultClient.getAliases();
}
return aliases.contains(alias);
}

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

@Override
public int engineSize() {
return aliases != null ? aliases.size() : 0;
int size = 0;
try {
size = DEFAULT_KEY_STORE.size();
} catch (KeyStoreException e) {
LOGGER.log(WARNING, "Unable to get the size of the jre key store.", e);
}
return size + (aliases != null ? aliases.size() : 0);
}

@Override
Expand Down Expand Up @@ -360,4 +426,87 @@ private void sideLoad() {
LOGGER.log(WARNING, "Unable to determine certificates to side-load", ioe);
}
}

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 final String KEY_PASSWORD = KEY_STORE_PASSWORD;

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

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

Path[] fileNames =
new Path[]{storeProp, DEFAULT_STORE};
for (Path fileName : fileNames) {
if (Files.exists(fileName) && Files.isReadable(fileName)) {
return fileName;
}
}
return null;
}

private static Key getKey(KeyStore ks, String alias) {
try {
return ks.getKey(alias, KEY_PASSWORD.toCharArray());
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
LOGGER.log(WARNING, "Unable to get the key from jre key store.", e);
}
return null;
}

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(
(PrivilegedAction<String>) () -> {
String value = System.getProperty(theProp, "");
return (value.isEmpty()) ? defaultVal : value;
});
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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")
);

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



}
}