Support Passing KeyManager and TrustManager into SslContextBuilder (#9805) (#9786)

Motivation:

This is a PR to solve the problem described here: https://github.com/netty/netty/issues/9767
Basically this PR is to add two more APIs in SslContextBuilder, for users to directly specify
the KeyManager or TrustManager they want to use when building SslContext. This is very helpful
when users want to pass in some customized implementation of KeyManager or TrustManager.

Modification:

This PR takes the first approach in here:
https://github.com/netty/netty/issues/9767#issuecomment-551927994 (comment)
which is to immediately convert the managers into factories and let factories continue to pass
through Netty.

1. Add in SslContextBuilder the two APIs mentioned above
2. Create a KeyManagerFactoryWrapper and a TrustManagerFactoryWrapper, which take a KeyManager
and a TrustManager respectively. These are two simple wrappers that do the conversion from
XXXManager class to XXXManagerFactory class
3.Create a SimpleKeyManagerFactory class(and internally X509KeyManagerWrapper for compatibility),
which hides the unnecessary details such as KeyManagerFactorySpi. This serves the similar
functionalities with SimpleTrustManagerFactory, which was already inside Netty.

Result:

Easier usage.
This commit is contained in:
ZhenLian 2019-11-26 05:25:13 -08:00 committed by Norman Maurer
parent e208e96f12
commit 6e3d784a2c
6 changed files with 486 additions and 0 deletions

View File

@ -0,0 +1,44 @@
/*
* Copyright 2019 The Netty Project
*
* The Netty Project 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.
*/
package io.netty.handler.ssl;
import io.netty.handler.ssl.util.SimpleKeyManagerFactory;
import io.netty.util.internal.ObjectUtil;
import java.security.KeyStore;
import javax.net.ssl.KeyManager;
import javax.net.ssl.ManagerFactoryParameters;
final class KeyManagerFactoryWrapper extends SimpleKeyManagerFactory {
private final KeyManager km;
KeyManagerFactoryWrapper(KeyManager km) {
this.km = ObjectUtil.checkNotNull(km, "km");
}
@Override
protected void engineInit(KeyStore keyStore, char[] var2) throws Exception { }
@Override
protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
throws Exception { }
@Override
protected KeyManager[] engineGetKeyManagers() {
return new KeyManager[] {km};
}
}

View File

@ -18,9 +18,11 @@ package io.netty.handler.ssl;
import io.netty.util.internal.UnstableApi; import io.netty.util.internal.UnstableApi;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
@ -160,6 +162,15 @@ public final class SslContextBuilder {
return new SslContextBuilder(true).keyManager(keyManagerFactory); return new SslContextBuilder(true).keyManager(keyManagerFactory);
} }
/**
* Creates a builder for new server-side {@link SslContext} with {@link KeyManager}.
*
* @param KeyManager non-{@code null} KeyManager for server's private key
*/
public static SslContextBuilder forServer(KeyManager keyManager) {
return new SslContextBuilder(true).keyManager(keyManager);
}
private final boolean forServer; private final boolean forServer;
private SslProvider provider; private SslProvider provider;
private Provider sslContextProvider; private Provider sslContextProvider;
@ -259,6 +270,19 @@ public final class SslContextBuilder {
return this; return this;
} }
/**
* A single trusted manager for verifying the remote endpoint's certificate.
* This is helpful when custom implementation of {@link TrustManager} is needed.
* Internally, a simple wrapper of {@link TrustManagerFactory} that only produces this
* specified {@link TrustManager} will be created, thus all the requirements specified in
* {@link #trustManager(TrustManagerFactory trustManagerFactory)} also apply here.
*/
public SslContextBuilder trustManager(TrustManager trustManager) {
this.trustManagerFactory = new TrustManagerFactoryWrapper(trustManager);
trustCertCollection = null;
return this;
}
/** /**
* Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
* be {@code null} for client contexts, which disables mutual authentication. * be {@code null} for client contexts, which disables mutual authentication.
@ -423,6 +447,28 @@ public final class SslContextBuilder {
return this; return this;
} }
/**
* A single key manager managing the identity information of this host.
* This is helpful when custom implementation of {@link KeyManager} is needed.
* Internally, a wrapper of {@link KeyManagerFactory} that only produces this specified
* {@link KeyManager} will be created, thus all the requirements specified in
* {@link #keyManager(KeyManagerFactory keyManagerFactory)} also apply here.
*/
public SslContextBuilder keyManager(KeyManager keyManager) {
if (forServer) {
checkNotNull(keyManager, "keyManager required for servers");
}
if (keyManager != null) {
this.keyManagerFactory = new KeyManagerFactoryWrapper(keyManager);
} else {
this.keyManagerFactory = null;
}
keyCertChain = null;
key = null;
keyPassword = null;
return this;
}
/** /**
* The cipher suites to enable, in the order of preference. {@code null} to use default * The cipher suites to enable, in the order of preference. {@code null} to use default
* cipher suites. * cipher suites.

View File

@ -0,0 +1,44 @@
/*
* Copyright 2019 The Netty Project
*
* The Netty Project 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.
*/
package io.netty.handler.ssl;
import io.netty.handler.ssl.util.SimpleTrustManagerFactory;
import io.netty.util.internal.ObjectUtil;
import java.security.KeyStore;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.TrustManager;
final class TrustManagerFactoryWrapper extends SimpleTrustManagerFactory {
private final TrustManager tm;
TrustManagerFactoryWrapper(TrustManager tm) {
this.tm = ObjectUtil.checkNotNull(tm, "tm");
}
@Override
protected void engineInit(KeyStore keyStore) throws Exception { }
@Override
protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
throws Exception { }
@Override
protected TrustManager[] engineGetTrustManagers() {
return new TrustManager[] {tm};
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright 2019 The Netty Project
*
* The Netty Project 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.
*/
package io.netty.handler.ssl.util;
import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SuppressJava6Requirement;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Provider;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.KeyManagerFactorySpi;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509KeyManager;
/**
* Helps to implement a custom {@link KeyManagerFactory}.
*/
public abstract class SimpleKeyManagerFactory extends KeyManagerFactory {
private static final Provider PROVIDER = new Provider("", 0.0, "") {
private static final long serialVersionUID = -2680540247105807895L;
};
/**
* {@link SimpleKeyManagerFactorySpi} must have a reference to {@link SimpleKeyManagerFactory}
* to delegate its callbacks back to {@link SimpleKeyManagerFactory}. However, it is impossible to do so,
* because {@link KeyManagerFactory} requires {@link KeyManagerFactorySpi} at construction time and
* does not provide a way to access it later.
*
* To work around this issue, we use an ugly hack which uses a {@link FastThreadLocal }.
*/
private static final FastThreadLocal<SimpleKeyManagerFactorySpi> CURRENT_SPI =
new FastThreadLocal<SimpleKeyManagerFactorySpi>() {
@Override
protected SimpleKeyManagerFactorySpi initialValue() {
return new SimpleKeyManagerFactorySpi();
}
};
/**
* Creates a new instance.
*/
protected SimpleKeyManagerFactory() {
this(StringUtil.EMPTY_STRING);
}
/**
* Creates a new instance.
*
* @param name the name of this {@link KeyManagerFactory}
*/
protected SimpleKeyManagerFactory(String name) {
super(CURRENT_SPI.get(), PROVIDER, ObjectUtil.checkNotNull(name, "name"));
CURRENT_SPI.get().init(this);
CURRENT_SPI.remove();
}
/**
* Initializes this factory with a source of certificate authorities and related key material.
*
* @see KeyManagerFactorySpi#engineInit(KeyStore, char[])
*/
protected abstract void engineInit(KeyStore keyStore, char[] var2) throws Exception;
/**
* Initializes this factory with a source of provider-specific key material.
*
* @see KeyManagerFactorySpi#engineInit(ManagerFactoryParameters)
*/
protected abstract void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception;
/**
* Returns one key manager for each type of key material.
*
* @see KeyManagerFactorySpi#engineGetKeyManagers()
*/
protected abstract KeyManager[] engineGetKeyManagers();
private static final class SimpleKeyManagerFactorySpi extends KeyManagerFactorySpi {
private SimpleKeyManagerFactory parent;
private volatile KeyManager[] keyManagers;
void init(SimpleKeyManagerFactory parent) {
this.parent = parent;
}
@Override
protected void engineInit(KeyStore keyStore, char[] pwd) throws KeyStoreException {
try {
parent.engineInit(keyStore, pwd);
} catch (KeyStoreException e) {
throw e;
} catch (Exception e) {
throw new KeyStoreException(e);
}
}
@Override
protected void engineInit(
ManagerFactoryParameters managerFactoryParameters) throws InvalidAlgorithmParameterException {
try {
parent.engineInit(managerFactoryParameters);
} catch (InvalidAlgorithmParameterException e) {
throw e;
} catch (Exception e) {
throw new InvalidAlgorithmParameterException(e);
}
}
@Override
protected KeyManager[] engineGetKeyManagers() {
KeyManager[] keyManagers = this.keyManagers;
if (keyManagers == null) {
keyManagers = parent.engineGetKeyManagers();
if (PlatformDependent.javaVersion() >= 7) {
wrapIfNeeded(keyManagers);
}
this.keyManagers = keyManagers;
}
return keyManagers.clone();
}
@SuppressJava6Requirement(reason = "Usage guarded by java version check")
private static void wrapIfNeeded(KeyManager[] keyManagers) {
for (int i = 0; i < keyManagers.length; i++) {
final KeyManager tm = keyManagers[i];
if (tm instanceof X509KeyManager && !(tm instanceof X509ExtendedKeyManager)) {
keyManagers[i] = new X509KeyManagerWrapper((X509KeyManager) tm);
}
}
}
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2019 The Netty Project
*
* The Netty Project 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.
*/
package io.netty.handler.ssl.util;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.util.internal.SuppressJava6Requirement;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509KeyManager;
@SuppressJava6Requirement(reason = "Usage guarded by java version check")
final class X509KeyManagerWrapper extends X509ExtendedKeyManager {
private final X509KeyManager delegate;
X509KeyManagerWrapper(X509KeyManager delegate) {
this.delegate = checkNotNull(delegate, "delegate");
}
@Override
public String[] getClientAliases(String var1, Principal[] var2) {
return delegate.getClientAliases(var1, var2);
}
@Override
public String chooseClientAlias(String[] var1, Principal[] var2, Socket var3) {
return delegate.chooseClientAlias(var1, var2, var3);
}
@Override
public String[] getServerAliases(String var1, Principal[] var2) {
return delegate.getServerAliases(var1, var2);
}
@Override
public String chooseServerAlias(String var1, Principal[] var2, Socket var3) {
return delegate.chooseServerAlias(var1, var2, var3);
}
@Override
public X509Certificate[] getCertificateChain(String var1) {
return delegate.getCertificateChain(var1);
}
@Override
public PrivateKey getPrivateKey(String var1) {
return delegate.getPrivateKey(var1);
}
@Override
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
return delegate.chooseClientAlias(keyType, issuers, null);
}
@Override
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
return delegate.chooseServerAlias(keyType, issuers, null);
}
}

View File

@ -21,9 +21,18 @@ import io.netty.util.CharsetUtil;
import org.junit.Assume; import org.junit.Assume;
import org.junit.Test; import org.junit.Test;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections; import java.util.Collections;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -85,6 +94,17 @@ public class SslContextBuilderTest {
testServerContext(SslProvider.OPENSSL); testServerContext(SslProvider.OPENSSL);
} }
@Test
public void testContextFromManagersJdk() throws Exception {
testContextFromManagers(SslProvider.JDK);
}
@Test
public void testContextFromManagersOpenssl() throws Exception {
Assume.assumeTrue(OpenSsl.isAvailable());
testContextFromManagers(SslProvider.OPENSSL);
}
@Test(expected = SSLException.class) @Test(expected = SSLException.class)
public void testUnsupportedPrivateKeyFailsFastForServer() throws Exception { public void testUnsupportedPrivateKeyFailsFastForServer() throws Exception {
Assume.assumeTrue(OpenSsl.isBoringSSL()); Assume.assumeTrue(OpenSsl.isBoringSSL());
@ -233,4 +253,104 @@ public class SslContextBuilderTest {
engine.closeInbound(); engine.closeInbound();
engine.closeOutbound(); engine.closeOutbound();
} }
private static void testContextFromManagers(SslProvider provider) throws Exception {
final SelfSignedCertificate cert = new SelfSignedCertificate();
KeyManager customKeyManager = new X509ExtendedKeyManager() {
@Override
public String[] getClientAliases(String s,
Principal[] principals) {
return new String[0];
}
@Override
public String chooseClientAlias(String[] strings,
Principal[] principals,
Socket socket) {
return "cert_sent_to_server";
}
@Override
public String[] getServerAliases(String s,
Principal[] principals) {
return new String[0];
}
@Override
public String chooseServerAlias(String s,
Principal[] principals,
Socket socket) {
return null;
}
@Override
public X509Certificate[] getCertificateChain(String s) {
X509Certificate[] certificates = new X509Certificate[1];
certificates[0] = cert.cert();
return new X509Certificate[0];
}
@Override
public PrivateKey getPrivateKey(String s) {
return cert.key();
}
};
TrustManager customTrustManager = new X509ExtendedTrustManager() {
@Override
public void checkClientTrusted(
X509Certificate[] x509Certificates, String s,
Socket socket) throws CertificateException { }
@Override
public void checkServerTrusted(
X509Certificate[] x509Certificates, String s,
Socket socket) throws CertificateException { }
@Override
public void checkClientTrusted(
X509Certificate[] x509Certificates, String s,
SSLEngine sslEngine) throws CertificateException { }
@Override
public void checkServerTrusted(
X509Certificate[] x509Certificates, String s,
SSLEngine sslEngine) throws CertificateException { }
@Override
public void checkClientTrusted(
X509Certificate[] x509Certificates, String s)
throws CertificateException { }
@Override
public void checkServerTrusted(
X509Certificate[] x509Certificates, String s)
throws CertificateException { }
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
SslContextBuilder client_builder = SslContextBuilder.forClient()
.sslProvider(provider)
.keyManager(customKeyManager)
.trustManager(customTrustManager)
.clientAuth(ClientAuth.OPTIONAL);
SslContext client_context = client_builder.build();
SSLEngine client_engine = client_context.newEngine(UnpooledByteBufAllocator.DEFAULT);
assertFalse(client_engine.getWantClientAuth());
assertFalse(client_engine.getNeedClientAuth());
client_engine.closeInbound();
client_engine.closeOutbound();
SslContextBuilder server_builder = SslContextBuilder.forServer(customKeyManager)
.sslProvider(provider)
.trustManager(customTrustManager)
.clientAuth(ClientAuth.REQUIRE);
SslContext server_context = server_builder.build();
SSLEngine server_engine = server_context.newEngine(UnpooledByteBufAllocator.DEFAULT);
assertFalse(server_engine.getWantClientAuth());
assertTrue(server_engine.getNeedClientAuth());
server_engine.closeInbound();
server_engine.closeOutbound();
}
} }