diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultKeyStore.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultKeyStore.java index 4de4d7293aafd..8a03ee3e8c852 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultKeyStore.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/KeyVaultKeyStore.java @@ -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; @@ -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; @@ -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 JRE_ALIASES; + /** * Constructor. * *

* The constructor uses System.getProperty for - * azure.keyvault.uri, - * azure.keyvault.aadAuthenticationUrl, + * azure.keyvault.uri, + * azure.keyvault.aadAuthenticationUrl, * azure.keyvault.tenantId, * azure.keyvault.clientId, * azure.keyvault.clientSecret and @@ -102,6 +119,19 @@ 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 @@ -109,7 +139,10 @@ public Enumeration engineAliases() { if (aliases == null) { aliases = keyVaultClient.getAliases(); } - return Collections.enumeration(aliases); + Set als = new HashSet<>(); + als.addAll(aliases); + als.addAll(JRE_ALIASES); + return Collections.enumeration(als); } @Override @@ -128,18 +161,24 @@ public boolean engineEntryInstanceOf(String alias, Class 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 @@ -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 @@ -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 value = System.getProperty(theProp, ""); + return (value.isEmpty()) ? defaultVal : value; + }); + } + } + } + } diff --git a/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java new file mode 100644 index 0000000000000..3001a82d58f1c --- /dev/null +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java @@ -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.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 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); + + + + } +}