HTTPCORE-682: Custom provider for key manager/trust manager initialization (#296)
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLContextBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLContextBuilder.java
index 6f8c478..886003b 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLContextBuilder.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/ssl/SSLContextBuilder.java
@@ -37,6 +37,7 @@
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Provider;
@@ -88,6 +89,8 @@
private String trustManagerFactoryAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
private SecureRandom secureRandom;
private Provider provider;
+ private Provider tsProvider;
+ private Provider ksProvider;
/**
* An empty immutable {@code KeyManager} array.
@@ -141,6 +144,56 @@
}
/**
+ * Sets the JCA provider to use for creating trust stores.
+ * @param provider provider to use for creating trust stores.
+ * @return this builder
+ * @since 5.2
+ */
+ public SSLContextBuilder setTrustStoreProvider(final Provider provider) {
+ this.tsProvider = provider;
+ return this;
+ }
+
+ /**
+ * Sets the JCA provider name to use for creating trust stores.
+ * @param name Name of the provider to use for creating trust stores, the provider must be registered with the JCA.
+ * @return this builder
+ * @since 5.2
+ */
+ public SSLContextBuilder setTrustStoreProvider(final String name) throws NoSuchProviderException {
+ this.tsProvider = Security.getProvider(name);
+ if (this.tsProvider == null) {
+ throw new NoSuchProviderException(name);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the JCA provider to use for creating key stores.
+ * @param provider provider to use for creating key stores.
+ * @return this builder
+ * @since 5.2
+ */
+ public SSLContextBuilder setKeyStoreProvider(final Provider provider) {
+ this.ksProvider = provider;
+ return this;
+ }
+
+ /**
+ * Sets the JCA provider name to use for creating key stores.
+ * @param name Name of the provider to use for creating key stores, the provider must be registered with the JCA.
+ * @return this builder
+ * @since 5.2
+ */
+ public SSLContextBuilder setKeyStoreProvider(final String name) throws NoSuchProviderException {
+ this.ksProvider = Security.getProvider(name);
+ if (this.ksProvider == null) {
+ throw new NoSuchProviderException(name);
+ }
+ return this;
+ }
+
+ /**
* Sets the key store type.
*
* @param keyStoreType
@@ -208,11 +261,15 @@
public SSLContextBuilder loadTrustMaterial(
final KeyStore truststore,
final TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyStoreException {
- final TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
- trustManagerFactoryAlgorithm == null ? TrustManagerFactory.getDefaultAlgorithm()
- : trustManagerFactoryAlgorithm);
- tmfactory.init(truststore);
- final TrustManager[] tms = tmfactory.getTrustManagers();
+
+ final String alg = trustManagerFactoryAlgorithm == null ?
+ TrustManagerFactory.getDefaultAlgorithm() : trustManagerFactoryAlgorithm;
+
+ final TrustManagerFactory tmFactory = tsProvider == null ?
+ TrustManagerFactory.getInstance(alg) : TrustManagerFactory.getInstance(alg, tsProvider);
+
+ tmFactory.init(truststore);
+ final TrustManager[] tms = tmFactory.getTrustManagers();
if (tms != null) {
if (trustStrategy != null) {
for (int i = 0; i < tms.length; i++) {
@@ -279,11 +336,15 @@
final char[] keyPassword,
final PrivateKeyStrategy aliasStrategy)
throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
- final KeyManagerFactory kmfactory = KeyManagerFactory
- .getInstance(keyManagerFactoryAlgorithm == null ? KeyManagerFactory.getDefaultAlgorithm()
- : keyManagerFactoryAlgorithm);
- kmfactory.init(keystore, keyPassword);
- final KeyManager[] kms = kmfactory.getKeyManagers();
+
+ final String alg = keyManagerFactoryAlgorithm == null ?
+ KeyManagerFactory.getDefaultAlgorithm() : keyManagerFactoryAlgorithm;
+
+ final KeyManagerFactory kmFactory = ksProvider == null ?
+ KeyManagerFactory.getInstance(alg) : KeyManagerFactory.getInstance(alg, ksProvider);
+
+ kmFactory.init(keystore, keyPassword);
+ final KeyManager[] kms = kmFactory.getKeyManagers();
if (kms != null) {
if (aliasStrategy != null) {
for (int i = 0; i < kms.length; i++) {
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/ssl/DummyProvider.java b/httpcore5/src/test/java/org/apache/hc/core5/ssl/DummyProvider.java
new file mode 100644
index 0000000..6d82a48
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/ssl/DummyProvider.java
@@ -0,0 +1,64 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.ssl;
+
+import java.security.Provider;
+import java.security.Security;
+import java.util.HashSet;
+import java.util.Set;
+
+public class DummyProvider extends Provider {
+
+ private final Provider realJSSEProvider = Security.getProvider(TestSSLContextBuilder.PROVIDER_SUN_JSSE);
+ private final Provider realJCEEProvider = Security.getProvider(TestSSLContextBuilder.PROVIDER_SUN_JCE);
+ final static String NAME = "FAKE";
+
+ private final Set<String> requestedTypes = new HashSet<>();
+
+ public DummyProvider() {
+ super(NAME, 1.1, "http core fake provider 1.1");
+ }
+
+ public boolean hasBeenRequested(final String what) {
+ return requestedTypes.contains(what);
+ }
+
+ @Override
+ public Service getService(final String type, final String algorithm) {
+ requestedTypes.add(type);
+ if ("KeyStore".equals(type)) {
+ return realJCEEProvider.getService(type, algorithm);
+ }
+ return realJSSEProvider.getService(type, algorithm);
+ }
+
+ @Override
+ public synchronized Set<Service> getServices() {
+ return realJSSEProvider.getServices();
+ }
+}
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/ssl/TestSSLContextBuilder.java b/httpcore5/src/test/java/org/apache/hc/core5/ssl/TestSSLContextBuilder.java
index 51cad2d..ca9671d 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/ssl/TestSSLContextBuilder.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/ssl/TestSSLContextBuilder.java
@@ -37,6 +37,7 @@
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.Security;
import java.security.UnrecoverableKeyException;
@@ -69,7 +70,8 @@
*/
public class TestSSLContextBuilder {
- private static final String PROVIDER_SUN_JSSE = "SunJSSE";
+ static final String PROVIDER_SUN_JSSE = "SunJSSE";
+ static final String PROVIDER_SUN_JCE = "SunJCE";
private static boolean isWindows() {
return System.getProperty("os.name").contains("Windows");
@@ -181,26 +183,115 @@
final URL resource1 = getResource("/test-server.p12");
final String storePassword = "nopassword";
final String keyPassword = "nopassword";
- final SSLContext sslContext = SSLContextBuilder.create()
- .setProvider(Security.getProvider(PROVIDER_SUN_JSSE))
+ final DummyProvider provider = new DummyProvider();
+ SSLContextBuilder.create()
+ .setProvider(provider)
.loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
.build();
- Assert.assertEquals(PROVIDER_SUN_JSSE, sslContext.getProvider().getName());
+ Assert.assertTrue(provider.hasBeenRequested("SSLContext"));
}
@Test
public void testBuildWithProviderName() throws Exception {
+
+ final DummyProvider provider = new DummyProvider();
+ Security.insertProviderAt(provider, 1);
+ try {
+
+ final URL resource1 = getResource("/test-server.p12");
+ final String storePassword = "nopassword";
+ final String keyPassword = "nopassword";
+ SSLContextBuilder.create()
+ .setProvider(DummyProvider.NAME)
+ .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
+ .build();
+ Assert.assertTrue(provider.hasBeenRequested("SSLContext"));
+
+ } finally {
+ Security.removeProvider(DummyProvider.NAME);
+ }
+ }
+
+ @Test
+ public void testBuildKSWithNoSuchProvider() {
+ Assert.assertThrows(NoSuchProviderException.class,
+ () -> SSLContextBuilder.create()
+ .setKeyStoreProvider("no-such-provider")
+ .build());
+ }
+
+ @Test
+ public void testBuildKSWithProvider() throws Exception {
final URL resource1 = getResource("/test-server.p12");
final String storePassword = "nopassword";
final String keyPassword = "nopassword";
- final SSLContext sslContext = SSLContextBuilder.create()
- .setProvider(PROVIDER_SUN_JSSE)
+ final DummyProvider provider = new DummyProvider();
+ SSLContextBuilder.create()
+ .setKeyStoreProvider(provider)
.loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
.build();
- Assert.assertEquals(PROVIDER_SUN_JSSE, sslContext.getProvider().getName());
+ Assert.assertTrue(provider.hasBeenRequested("KeyManagerFactory"));
}
@Test
+ public void testBuildKSWithProviderName() throws Exception {
+
+ final DummyProvider provider = new DummyProvider();
+ Security.insertProviderAt(provider, 1);
+ try {
+
+ final URL resource1 = getResource("/test-server.p12");
+ final String storePassword = "nopassword";
+ final String keyPassword = "nopassword";
+ SSLContextBuilder.create()
+ .setKeyStoreProvider(DummyProvider.NAME)
+ .loadKeyMaterial(resource1, storePassword.toCharArray(), keyPassword.toCharArray())
+ .build();
+ Assert.assertTrue(provider.hasBeenRequested("KeyManagerFactory"));
+
+ } finally {
+ Security.removeProvider(DummyProvider.NAME);
+ }
+ }
+
+ @Test
+ public void testBuildTSWithNoSuchProvider() {
+ Assert.assertThrows(NoSuchProviderException.class, ()->
+ SSLContextBuilder.create()
+ .setTrustStoreProvider("no-such-provider")
+ .build());
+ }
+
+ @Test
+ public void testBuildTSWithProvider() throws Exception {
+ final DummyProvider provider = new DummyProvider();
+ SSLContextBuilder.create()
+ .setTrustStoreProvider(provider)
+ .loadTrustMaterial((KeyStore) null, null)
+ .build();
+ Assert.assertTrue(provider.hasBeenRequested("TrustManagerFactory"));
+ }
+
+ @Test
+ public void testBuildTSWithProviderName() throws Exception {
+
+ final DummyProvider provider = new DummyProvider();
+ Security.insertProviderAt(provider, 1);
+ try {
+
+ SSLContextBuilder.create()
+ .setTrustStoreProvider(DummyProvider.NAME)
+ .loadTrustMaterial((KeyStore) null, null)
+ .build();
+ Assert.assertTrue(provider.hasBeenRequested("TrustManagerFactory"));
+
+ } finally {
+ Security.removeProvider(DummyProvider.NAME);
+ }
+ }
+
+
+ @Test
public void testKeyWithAlternatePasswordInvalid() throws Exception {
final URL resource1 = getResource("/test-keypasswd.p12");
final String storePassword = "nopassword";