Add OpenSslX509KeyManagerFactory which makes it even easier for peopl… (#8084)

* Add OpenSslX509KeyManagerFactory which makes it even easier for people to get the maximum performance when using OpenSSL / LibreSSL / BoringSSL  with netty.

Motivation:

To make it even easier for people to get the maximum performance when using native SSL we should provide our own KeyManagerFactory implementation that people can just use to configure their key material.

Modifications:

- Add OpenSslX509KeyManagerFactory which users can use for maximum performance with native SSL
- Refactor some internal code to re-use logic and not duplicate it.

Result:

Easier to get the max performance out of native SSL implementation.
This commit is contained in:
Norman Maurer 2018-07-10 00:42:37 -04:00 committed by GitHub
parent 6afab517b0
commit a137291ad1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 256 additions and 11 deletions

View File

@ -0,0 +1,218 @@
/*
* Copyright 2018 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.buffer.ByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.ObjectUtil;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.KeyManagerFactorySpi;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.X509KeyManager;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.UnrecoverableKeyException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Special {@link KeyManagerFactory} that pre-compute the keymaterial used when {@link SslProvider#OPENSSL} or
* {@link SslProvider#OPENSSL_REFCNT} is used and so will improve handshake times and its performance.
*
* Because the keymaterial is pre-computed any modification to the {@link KeyStore} is ignored after
* {@link #init(KeyStore, char[])} is called.
*
* {@link #init(ManagerFactoryParameters)} is not supported by this implementation and so a call to it will always
* result in an {@link InvalidAlgorithmParameterException}.
*/
public final class OpenSslX509KeyManagerFactory extends KeyManagerFactory {
private final OpenSslKeyManagerFactorySpi spi;
public OpenSslX509KeyManagerFactory() {
this(newOpenSslKeyManagerFactorySpi(null));
}
public OpenSslX509KeyManagerFactory(Provider provider) {
this(newOpenSslKeyManagerFactorySpi(provider));
}
public OpenSslX509KeyManagerFactory(String algorithm, Provider provider) throws NoSuchAlgorithmException {
this(newOpenSslKeyManagerFactorySpi(algorithm, provider));
}
private OpenSslX509KeyManagerFactory(OpenSslKeyManagerFactorySpi spi) {
super(spi, spi.kmf.getProvider(), spi.kmf.getAlgorithm());
this.spi = spi;
}
private static OpenSslKeyManagerFactorySpi newOpenSslKeyManagerFactorySpi(Provider provider) {
try {
return newOpenSslKeyManagerFactorySpi(null, provider);
} catch (NoSuchAlgorithmException e) {
// This should never happen as we use the default algorithm.
throw new IllegalStateException(e);
}
}
private static OpenSslKeyManagerFactorySpi newOpenSslKeyManagerFactorySpi(String algorithm, Provider provider)
throws NoSuchAlgorithmException {
if (algorithm == null) {
algorithm = KeyManagerFactory.getDefaultAlgorithm();
}
return new OpenSslKeyManagerFactorySpi(
provider == null ? KeyManagerFactory.getInstance(algorithm) :
KeyManagerFactory.getInstance(algorithm, provider));
}
OpenSslKeyMaterialProvider newProvider() {
return spi.newProvider();
}
private static final class OpenSslKeyManagerFactorySpi extends KeyManagerFactorySpi {
final KeyManagerFactory kmf;
private volatile ProviderFactory providerFactory;
OpenSslKeyManagerFactorySpi(KeyManagerFactory kmf) {
this.kmf = ObjectUtil.checkNotNull(kmf, "kmf");
}
@Override
protected synchronized void engineInit(KeyStore keyStore, char[] chars)
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
if (providerFactory != null) {
throw new KeyStoreException("Already initialized");
}
if (!keyStore.aliases().hasMoreElements()) {
throw new KeyStoreException("No aliases found");
}
kmf.init(keyStore, chars);
providerFactory = new ProviderFactory(
ReferenceCountedOpenSslContext.chooseX509KeyManager(kmf.getKeyManagers()),
password(chars), Collections.list(keyStore.aliases()));
}
private static String password(char[] password) {
if (password == null || password.length == 0) {
return null;
}
return new String(password);
}
@Override
protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
throws InvalidAlgorithmParameterException {
throw new InvalidAlgorithmParameterException("Not supported");
}
@Override
protected KeyManager[] engineGetKeyManagers() {
ProviderFactory providerFactory = this.providerFactory;
if (providerFactory == null) {
throw new IllegalStateException("engineInit(...) not called yet");
}
return new KeyManager[] { providerFactory.keyManager };
}
OpenSslKeyMaterialProvider newProvider() {
ProviderFactory providerFactory = this.providerFactory;
if (providerFactory == null) {
throw new IllegalStateException("engineInit(...) not called yet");
}
return providerFactory.newProvider();
}
private static final class ProviderFactory {
private final X509KeyManager keyManager;
private final String password;
private final Iterable<String> aliases;
ProviderFactory(X509KeyManager keyManager, String password, Iterable<String> aliases) {
this.keyManager = keyManager;
this.password = password;
this.aliases = aliases;
}
OpenSslKeyMaterialProvider newProvider() {
return new OpenSslPopulatedKeyMaterialProvider(keyManager,
password, aliases);
}
/**
* {@link OpenSslKeyMaterialProvider} implementation that pre-compute the {@link OpenSslKeyMaterial} for
* all aliases.
*/
private static final class OpenSslPopulatedKeyMaterialProvider extends OpenSslKeyMaterialProvider {
private final Map<String, Object> materialMap;
OpenSslPopulatedKeyMaterialProvider(
X509KeyManager keyManager, String password, Iterable<String> aliases) {
super(keyManager, password);
materialMap = new HashMap<String, Object>();
boolean initComplete = false;
try {
for (String alias: aliases) {
if (alias != null && !materialMap.containsKey(alias)) {
try {
materialMap.put(alias, super.chooseKeyMaterial(
UnpooledByteBufAllocator.DEFAULT, alias));
} catch (Exception e) {
// Just store the exception and rethrow it when we try to choose the keymaterial
// for this alias later on.
materialMap.put(alias, e);
}
}
}
initComplete = true;
} finally {
if (!initComplete) {
destroy();
}
}
if (materialMap.isEmpty()) {
throw new IllegalArgumentException("aliases must be non-empty");
}
}
@Override
OpenSslKeyMaterial chooseKeyMaterial(ByteBufAllocator allocator, String alias) throws Exception {
Object value = materialMap.get(alias);
if (value instanceof OpenSslKeyMaterial) {
return ((OpenSslKeyMaterial) value).retain();
} else {
throw (Exception) value;
}
}
@Override
void destroy() {
for (Object material: materialMap.values()) {
ReferenceCountUtil.release(material);
}
materialMap.clear();
}
}
}
}
}

View File

@ -102,9 +102,16 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
} else {
// javadocs state that keyManagerFactory has precedent over keyCertChain
if (keyManagerFactory == null && keyCertChain != null) {
keyMaterialProvider = new OpenSslCachingKeyMaterialProvider(
chooseX509KeyManager(buildKeyManagerFactory(keyCertChain, key, keyPassword, null)
.getKeyManagers()), keyPassword);
char[] keyPasswordChars = keyStorePassword(keyPassword);
KeyStore ks = buildKeyStore(keyCertChain, key, keyPasswordChars);
if (ks.aliases().hasMoreElements()) {
keyManagerFactory = new OpenSslX509KeyManagerFactory();
} else {
keyManagerFactory = new OpenSslCachingX509KeyManagerFactory(
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()));
}
keyManagerFactory.init(ks, keyPasswordChars);
keyMaterialProvider = providerFor(keyManagerFactory, keyPassword);
} else if (keyManagerFactory != null) {
keyMaterialProvider = providerFor(keyManagerFactory, keyPassword);
}

View File

@ -823,6 +823,10 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
* ensure that the same material is always returned for the same alias.
*/
static OpenSslKeyMaterialProvider providerFor(KeyManagerFactory factory, String password) {
if (factory instanceof OpenSslX509KeyManagerFactory) {
return ((OpenSslX509KeyManagerFactory) factory).newProvider();
}
X509KeyManager keyManager = chooseX509KeyManager(factory.getKeyManagers());
if (factory instanceof OpenSslCachingX509KeyManagerFactory) {
// The user explicit used OpenSslCachingX509KeyManagerFactory which signals us that its fine to cache.

View File

@ -121,12 +121,17 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
// javadocs state that keyManagerFactory has precedent over keyCertChain, and we must have a
// keyManagerFactory for the server so build one if it is not specified.
if (keyManagerFactory == null) {
keyMaterialProvider = new OpenSslCachingKeyMaterialProvider(
chooseX509KeyManager(buildKeyManagerFactory(keyCertChain, key, keyPassword, null)
.getKeyManagers()), keyPassword);
} else {
keyMaterialProvider = providerFor(keyManagerFactory, keyPassword);
char[] keyPasswordChars = keyStorePassword(keyPassword);
KeyStore ks = buildKeyStore(keyCertChain, key, keyPasswordChars);
if (ks.aliases().hasMoreElements()) {
keyManagerFactory = new OpenSslX509KeyManagerFactory();
} else {
keyManagerFactory = new OpenSslCachingX509KeyManagerFactory(
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()));
}
keyManagerFactory.init(ks, keyPasswordChars);
}
keyMaterialProvider = providerFor(keyManagerFactory, keyPassword);
result.keyMaterialManager = new OpenSslKeyMaterialManager(keyMaterialProvider);
}

View File

@ -1154,8 +1154,15 @@ public abstract class SslContext {
String keyPassword, KeyManagerFactory kmf)
throws KeyStoreException, NoSuchAlgorithmException, IOException,
CertificateException, UnrecoverableKeyException {
char[] keyPasswordChars = keyPassword == null ? EmptyArrays.EMPTY_CHARS : keyPassword.toCharArray();
char[] keyPasswordChars = keyStorePassword(keyPassword);
KeyStore ks = buildKeyStore(certChainFile, key, keyPasswordChars);
return buildKeyManagerFactory(ks, keyAlgorithm, keyPasswordChars, kmf);
}
static KeyManagerFactory buildKeyManagerFactory(KeyStore ks,
String keyAlgorithm,
char[] keyPasswordChars, KeyManagerFactory kmf)
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
// Set up key manager factory to use our key store
if (kmf == null) {
kmf = KeyManagerFactory.getInstance(keyAlgorithm);
@ -1164,4 +1171,8 @@ public abstract class SslContext {
return kmf;
}
static char[] keyStorePassword(String keyPassword) {
return keyPassword == null ? EmptyArrays.EMPTY_CHARS : keyPassword.toCharArray();
}
}

View File

@ -122,7 +122,7 @@ public final class SslContextBuilder {
* Creates a builder for new server-side {@link SslContext}.
*
* If you use {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} consider using
* {@link OpenSslCachingX509KeyManagerFactory}.
* {@link OpenSslX509KeyManagerFactory} or {@link OpenSslCachingX509KeyManagerFactory}.
*
* @param keyManagerFactory non-{@code null} factory for server's private key
* @see #keyManager(KeyManagerFactory)
@ -340,7 +340,7 @@ public final class SslContextBuilder {
* you must use {@link #keyManager(File, File)} or {@link #keyManager(File, File, String)}.
*
* If you use {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} consider using
* {@link OpenSslCachingX509KeyManagerFactory}.
* {@link OpenSslX509KeyManagerFactory} or {@link OpenSslCachingX509KeyManagerFactory}.
*/
public SslContextBuilder keyManager(KeyManagerFactory keyManagerFactory) {
if (forServer) {