diff --git a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml index aec266cb68b6d..b05c56ddaaf3c 100755 --- a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml +++ b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml @@ -141,6 +141,15 @@ + + + + + + + + + diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/JreCertificates.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/JreCertificates.java new file mode 100644 index 0000000000000..711000f049aaa --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/JreCertificates.java @@ -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 aliases; + + /** + * Stores the jre key store certificates. + */ + private final Map certs; + + /** + * Stores the jre key store keys + */ + private final Map 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 getAliases() { + return aliases; + } + + @Override + public Map getCertificates() { + return certs; + } + + @Override + public Map 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 value = System.getProperty(theProp, ""); + return (value.isEmpty()) ? defaultVal : value; + }); + } + } +} 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 a84597b0a3b42..3704aaaae1c97 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 @@ -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; @@ -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 allCertificates; + /** * Stores the creation date. */ @@ -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 engineAliases() { - List aliasList = Stream.of(keyVaultCertificates, classpathCertificates) + List aliasList = allCertificates.stream() .map(AzureCertificates::getAliases) .flatMap(Collection::stream) - .distinct().collect(Collectors.toList()); + .distinct() + .collect(Collectors.toList()); return Collections.enumeration(aliasList); } @@ -141,7 +154,7 @@ public boolean engineEntryInstanceOf(String alias, Class a.containsKey(alias)) .findFirst() @@ -159,7 +172,7 @@ public Certificate engineGetCertificate(String alias) { public String engineGetCertificateAlias(Certificate cert) { String alias = null; if (cert != null) { - List aliasList = Stream.of(keyVaultCertificates, classpathCertificates) + List aliasList = allCertificates.stream() .map(AzureCertificates::getAliases) .flatMap(Collection::stream) .distinct() @@ -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() @@ -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() @@ -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() 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..65dd4ae18fc55 --- /dev/null +++ b/sdk/keyvault/azure-security-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java @@ -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 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.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); + } +}