-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add jre key store certificates to key vault key store, and add test f…
…or it. (#21845)
- Loading branch information
1 parent
387e742
commit a2209fd
Showing
4 changed files
with
311 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
168 changes: 168 additions & 0 deletions
168
...-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/JreCertificates.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
...rity-test-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/JreKeyStoreTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |