Allow to cache keymaterial when using OpenSSL

Motiviation:

During profiling it showed that a lot of time during the handshake is spent by parsing the key / chain over and over again. We should cache these parsed structures if possible to reduce the overhead during handshake.

Modification:

- Use new APIs provided by https://github.com/netty/netty-tcnative/pull/360.
- Introduce OpensslStaticX509KeyManagerFactory which allows to wrap another KeyManagerFactory and caches the key material provided by it.

Result:

In benchmarks handshake times have improved by 30 %.
This commit is contained in:
Norman Maurer 2018-06-19 18:46:13 +02:00
parent 4a8d3a274c
commit 0337ecdcc8
16 changed files with 751 additions and 275 deletions

View File

@ -0,0 +1,68 @@
/*
* 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 javax.net.ssl.X509KeyManager;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* {@link OpenSslKeyMaterialProvider} that will cache the {@link OpenSslKeyMaterial} to reduce the overhead
* of parsing the chain and the key for generation of the material.
*/
final class OpenSslCachingKeyMaterialProvider extends OpenSslKeyMaterialProvider {
private final ConcurrentMap<String, OpenSslKeyMaterial> cache = new ConcurrentHashMap<String, OpenSslKeyMaterial>();
OpenSslCachingKeyMaterialProvider(X509KeyManager keyManager, String password) {
super(keyManager, password);
}
@Override
OpenSslKeyMaterial chooseKeyMaterial(ByteBufAllocator allocator, String alias) throws Exception {
OpenSslKeyMaterial material = cache.get(alias);
if (material == null) {
material = super.chooseKeyMaterial(allocator, alias);
if (material == null) {
// No keymaterial should be used.
return null;
}
OpenSslKeyMaterial old = cache.putIfAbsent(alias, material);
if (old != null) {
material.release();
material = old;
}
}
// We need to call retain() as we want to always have at least a refCnt() of 1 before destroy() was called.
return material.retain();
}
@Override
void destroy() {
// Remove and release all entries.
do {
Iterator<OpenSslKeyMaterial> iterator = cache.values().iterator();
while (iterator.hasNext()) {
iterator.next().release();
iterator.remove();
}
} while (!cache.isEmpty());
}
}

View File

@ -0,0 +1,60 @@
/*
* 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 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.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.X509Certificate;
/**
* Wraps another {@link KeyManagerFactory} and caches its chains / certs for an alias for better performance when using
* {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT}.
*
* Because of the caching its important that the wrapped {@link KeyManagerFactory}s {@link X509KeyManager}s always
* return the same {@link X509Certificate} chain and {@link PrivateKey} for the same alias.
*/
public final class OpenSslCachingX509KeyManagerFactory extends KeyManagerFactory {
public OpenSslCachingX509KeyManagerFactory(final KeyManagerFactory factory) {
super(new KeyManagerFactorySpi() {
@Override
protected void engineInit(KeyStore keyStore, char[] chars)
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
factory.init(keyStore, chars);
}
@Override
protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
throws InvalidAlgorithmParameterException {
factory.init(managerFactoryParameters);
}
@Override
protected KeyManager[] engineGetKeyManagers() {
return factory.getKeyManagers();
}
}, factory.getProvider(), factory.getAlgorithm());
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright 2016 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 javax.net.ssl.X509ExtendedKeyManager;
import javax.security.auth.x500.X500Principal;
final class OpenSslExtendedKeyMaterialManager extends OpenSslKeyMaterialManager {
private final X509ExtendedKeyManager keyManager;
OpenSslExtendedKeyMaterialManager(X509ExtendedKeyManager keyManager, String password) {
super(keyManager, password);
this.keyManager = keyManager;
}
@Override
protected String chooseClientAlias(ReferenceCountedOpenSslEngine engine, String[] keyTypes,
X500Principal[] issuer) {
return keyManager.chooseEngineClientAlias(keyTypes, issuer, engine);
}
@Override
protected String chooseServerAlias(ReferenceCountedOpenSslEngine engine, String type) {
return keyManager.chooseEngineServerAlias(type, null, engine);
}
}

View File

@ -0,0 +1,124 @@
/*
* 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.internal.tcnative.SSL;
import io.netty.util.AbstractReferenceCounted;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.ResourceLeakTracker;
/**
* Holds references to the native key-material that is used by OpenSSL.
*/
final class OpenSslKeyMaterial extends AbstractReferenceCounted {
private static final ResourceLeakDetector<OpenSslKeyMaterial> leakDetector =
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(OpenSslKeyMaterial.class);
private final ResourceLeakTracker<OpenSslKeyMaterial> leak;
private long chain;
private long privateKey;
OpenSslKeyMaterial(long chain, long privateKey) {
this.chain = chain;
this.privateKey = privateKey;
leak = leakDetector.track(this);
}
/**
* Returns the pointer to the {@code STACK_OF(X509)} which holds the certificate chain.
*/
public long certificateChainAddress() {
if (refCnt() <= 0) {
throw new IllegalReferenceCountException();
}
return chain;
}
/**
* Returns the pointer to the {@code EVP_PKEY}.
*/
public long privateKeyAddress() {
if (refCnt() <= 0) {
throw new IllegalReferenceCountException();
}
return privateKey;
}
@Override
protected void deallocate() {
SSL.freeX509Chain(chain);
chain = 0;
SSL.freePrivateKey(privateKey);
privateKey = 0;
if (leak != null) {
boolean closed = leak.close(this);
assert closed;
}
}
@Override
public OpenSslKeyMaterial retain() {
if (leak != null) {
leak.record();
}
super.retain();
return this;
}
@Override
public OpenSslKeyMaterial retain(int increment) {
if (leak != null) {
leak.record();
}
super.retain(increment);
return this;
}
@Override
public OpenSslKeyMaterial touch() {
if (leak != null) {
leak.record();
}
super.touch();
return this;
}
@Override
public OpenSslKeyMaterial touch(Object hint) {
if (leak != null) {
leak.record(hint);
}
return this;
}
@Override
public boolean release() {
if (leak != null) {
leak.record();
}
return super.release();
}
@Override
public boolean release(int decrement) {
if (leak != null) {
leak.record();
}
return super.release(decrement);
}
}

View File

@ -15,11 +15,10 @@
*/
package io.netty.handler.ssl;
import io.netty.buffer.ByteBufAllocator;
import io.netty.internal.tcnative.CertificateRequestedCallback;
import io.netty.internal.tcnative.SSL;
import javax.net.ssl.SSLException;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509KeyManager;
import javax.security.auth.x500.X500Principal;
import java.security.PrivateKey;
@ -29,14 +28,12 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.freeBio;
import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.toBIO;
/**
* Manages key material for {@link OpenSslEngine}s and so set the right {@link PrivateKey}s and
* {@link X509Certificate}s.
*/
class OpenSslKeyMaterialManager {
final class OpenSslKeyMaterialManager {
// Code in this class is inspired by code of conscrypts:
// - https://android.googlesource.com/platform/external/
@ -62,15 +59,13 @@ class OpenSslKeyMaterialManager {
KEY_TYPES.put("DH_RSA", KEY_TYPE_DH_RSA);
}
private final X509KeyManager keyManager;
private final String password;
private final OpenSslKeyMaterialProvider provider;
OpenSslKeyMaterialManager(X509KeyManager keyManager, String password) {
this.keyManager = keyManager;
this.password = password;
OpenSslKeyMaterialManager(OpenSslKeyMaterialProvider provider) {
this.provider = provider;
}
void setKeyMaterial(ReferenceCountedOpenSslEngine engine) throws SSLException {
void setKeyMaterialServerSide(ReferenceCountedOpenSslEngine engine) throws SSLException {
long ssl = engine.sslPointer();
String[] authMethods = SSL.authenticationMethods(ssl);
Set<String> aliases = new HashSet<String>(authMethods.length);
@ -79,101 +74,62 @@ class OpenSslKeyMaterialManager {
if (type != null) {
String alias = chooseServerAlias(engine, type);
if (alias != null && aliases.add(alias)) {
setKeyMaterial(ssl, alias, engine.alloc);
OpenSslKeyMaterial keyMaterial = null;
try {
keyMaterial = provider.chooseKeyMaterial(engine.alloc, alias);
if (keyMaterial != null) {
SSL.setKeyMaterialServerSide(
ssl, keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress());
}
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException(e);
} finally {
if (keyMaterial != null) {
keyMaterial.release();
}
}
}
}
}
}
CertificateRequestedCallback.KeyMaterial keyMaterial(ReferenceCountedOpenSslEngine engine, String[] keyTypes,
X500Principal[] issuer) throws SSLException {
void setKeyMaterialClientSide(ReferenceCountedOpenSslEngine engine, long certOut, long keyOut, String[] keyTypes,
X500Principal[] issuer) throws SSLException {
String alias = chooseClientAlias(engine, keyTypes, issuer);
long keyBio = 0;
long keyCertChainBio = 0;
long pkey = 0;
long certChain = 0;
OpenSslKeyMaterial keyMaterial = null;
try {
// TODO: Should we cache these and so not need to do a memory copy all the time ?
X509Certificate[] certificates = keyManager.getCertificateChain(alias);
if (certificates == null || certificates.length == 0) {
return null;
}
PrivateKey key = keyManager.getPrivateKey(alias);
keyCertChainBio = toBIO(engine.alloc, certificates);
certChain = SSL.parseX509Chain(keyCertChainBio);
if (key != null) {
keyBio = toBIO(engine.alloc, key);
pkey = SSL.parsePrivateKey(keyBio, password);
}
CertificateRequestedCallback.KeyMaterial material = new CertificateRequestedCallback.KeyMaterial(
certChain, pkey);
// Reset to 0 so we do not free these. This is needed as the client certificate callback takes ownership
// of both the key and the certificate if they are returned from this method, and thus must not
// be freed here.
certChain = pkey = 0;
return material;
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException(e);
} finally {
freeBio(keyBio);
freeBio(keyCertChainBio);
SSL.freePrivateKey(pkey);
SSL.freeX509Chain(certChain);
}
}
private void setKeyMaterial(long ssl, String alias, ByteBufAllocator allocator) throws SSLException {
long keyBio = 0;
long keyCertChainBio = 0;
long keyCertChainBio2 = 0;
try {
// TODO: Should we cache these and so not need to do a memory copy all the time ?
X509Certificate[] certificates = keyManager.getCertificateChain(alias);
if (certificates == null || certificates.length == 0) {
return;
}
PrivateKey key = keyManager.getPrivateKey(alias);
// Only encode one time
PemEncoded encoded = PemX509Certificate.toPEM(allocator, true, certificates);
try {
keyCertChainBio = toBIO(allocator, encoded.retain());
keyCertChainBio2 = toBIO(allocator, encoded.retain());
if (key != null) {
keyBio = toBIO(allocator, key);
}
SSL.setCertificateBio(ssl, keyCertChainBio, keyBio, password);
// We may have more then one cert in the chain so add all of them now.
SSL.setCertificateChainBio(ssl, keyCertChainBio2, true);
} finally {
encoded.release();
keyMaterial = provider.chooseKeyMaterial(engine.alloc, alias);
if (keyMaterial != null) {
SSL.setKeyMaterialClientSide(engine.sslPointer(), certOut, keyOut,
keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress());
}
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException(e);
} finally {
freeBio(keyBio);
freeBio(keyCertChainBio);
freeBio(keyCertChainBio2);
if (keyMaterial != null) {
keyMaterial.release();
}
}
}
protected String chooseClientAlias(@SuppressWarnings("unused") ReferenceCountedOpenSslEngine engine,
private String chooseClientAlias(ReferenceCountedOpenSslEngine engine,
String[] keyTypes, X500Principal[] issuer) {
return keyManager.chooseClientAlias(keyTypes, issuer, null);
X509KeyManager manager = provider.keyManager();
if (manager instanceof X509ExtendedKeyManager) {
return ((X509ExtendedKeyManager) manager).chooseEngineClientAlias(keyTypes, issuer, engine);
}
return manager.chooseClientAlias(keyTypes, issuer, null);
}
protected String chooseServerAlias(@SuppressWarnings("unused") ReferenceCountedOpenSslEngine engine, String type) {
return keyManager.chooseServerAlias(type, null, null);
private String chooseServerAlias(ReferenceCountedOpenSslEngine engine, String type) {
X509KeyManager manager = provider.keyManager();
if (manager instanceof X509ExtendedKeyManager) {
return ((X509ExtendedKeyManager) manager).chooseEngineServerAlias(type, null, engine);
}
return manager.chooseServerAlias(type, null, null);
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.internal.tcnative.SSL;
import javax.net.ssl.X509KeyManager;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.toBIO;
/**
* Provides {@link OpenSslKeyMaterial} for a given alias.
*/
class OpenSslKeyMaterialProvider {
private final X509KeyManager keyManager;
private final String password;
OpenSslKeyMaterialProvider(X509KeyManager keyManager, String password) {
this.keyManager = keyManager;
this.password = password;
}
/**
* Returns the underlying {@link X509KeyManager} that is used.
*/
X509KeyManager keyManager() {
return keyManager;
}
/**
* Returns the {@link OpenSslKeyMaterial} or {@code null} (if none) that should be used during the handshake by
* OpenSSL.
*/
OpenSslKeyMaterial chooseKeyMaterial(ByteBufAllocator allocator, String alias) throws Exception {
X509Certificate[] certificates = keyManager.getCertificateChain(alias);
if (certificates == null || certificates.length == 0) {
return null;
}
PrivateKey key = keyManager.getPrivateKey(alias);
PemEncoded encoded = PemX509Certificate.toPEM(allocator, true, certificates);
long chainBio = 0;
long pkeyBio = 0;
long chain = 0;
long pkey = 0;
try {
chainBio = toBIO(allocator, encoded.retain());
pkeyBio = toBIO(allocator, key);
chain = SSL.parseX509Chain(chainBio);
pkey = key == null ? 0 : SSL.parsePrivateKey(pkeyBio, password);
OpenSslKeyMaterial keyMaterial = new OpenSslKeyMaterial(chain, pkey);
// See the chain and pkey to 0 so we will not release it as the ownership was
// transferred to OpenSslKeyMaterial.
chain = 0;
pkey = 0;
return keyMaterial;
} finally {
SSL.freeBIO(chainBio);
SSL.freeBIO(pkeyBio);
if (chain != 0) {
SSL.freeX509Chain(chain);
}
if (pkey != 0) {
SSL.freePrivateKey(pkey);
}
encoded.release();
}
}
/**
* Will be invoked once the provider should be destroyed.
*/
void destroy() {
// NOOP.
}
}

View File

@ -25,8 +25,8 @@ import java.util.concurrent.locks.Lock;
* {@link OpenSslSessionContext} implementation which offers extra methods which are only useful for the server-side.
*/
public final class OpenSslServerSessionContext extends OpenSslSessionContext {
OpenSslServerSessionContext(ReferenceCountedOpenSslContext context) {
super(context);
OpenSslServerSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
super(context, provider);
}
@Override

View File

@ -34,14 +34,21 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
private static final Enumeration<byte[]> EMPTY = new EmptyEnumeration();
private final OpenSslSessionStats stats;
// The OpenSslKeyMaterialProvider is not really used by the OpenSslSessionContext but only be stored here
// to make it easier to destroy it later because the ReferenceCountedOpenSslContext will hold a reference
// to OpenSslSessionContext.
private final OpenSslKeyMaterialProvider provider;
final ReferenceCountedOpenSslContext context;
// IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent
// the GC to collect OpenSslContext as this would also free the pointer and so could result in a
// segfault when the user calls any of the methods here that try to pass the pointer down to the native
// level.
OpenSslSessionContext(ReferenceCountedOpenSslContext context) {
OpenSslSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
this.context = context;
this.provider = provider;
stats = new OpenSslSessionStats(context);
}
@ -123,6 +130,12 @@ public abstract class OpenSslSessionContext implements SSLSessionContext {
return stats;
}
final void destroy() {
if (provider != null) {
provider.destroy();
}
}
private static final class EmptyEnumeration implements Enumeration<byte[]> {
@Override
public boolean hasMoreElements() {

View File

@ -31,9 +31,7 @@ import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
@ -90,71 +88,82 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
throw new IllegalArgumentException(
"Either both keyCertChain and key needs to be null or none of them");
}
OpenSslKeyMaterialProvider keyMaterialProvider = null;
try {
if (!OpenSsl.useKeyManagerFactory()) {
if (keyManagerFactory != null) {
throw new IllegalArgumentException(
"KeyManagerFactory not supported");
}
if (keyCertChain != null/* && key != null*/) {
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
}
} else {
// javadocs state that keyManagerFactory has precedent over keyCertChain
if (keyManagerFactory == null && keyCertChain != null) {
keyManagerFactory = buildKeyManagerFactory(
keyCertChain, key, keyPassword, keyManagerFactory);
}
try {
if (!OpenSsl.useKeyManagerFactory()) {
if (keyManagerFactory != null) {
throw new IllegalArgumentException(
"KeyManagerFactory not supported");
}
if (keyCertChain != null/* && key != null*/) {
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
}
} 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);
} else if (keyManagerFactory != null) {
keyMaterialProvider = providerFor(keyManagerFactory, keyPassword);
}
if (keyManagerFactory != null) {
X509KeyManager keyManager = chooseX509KeyManager(keyManagerFactory.getKeyManagers());
OpenSslKeyMaterialManager materialManager = useExtendedKeyManager(keyManager) ?
new OpenSslExtendedKeyMaterialManager(
(X509ExtendedKeyManager) keyManager, keyPassword) :
new OpenSslKeyMaterialManager(keyManager, keyPassword);
SSLContext.setCertRequestedCallback(ctx, new OpenSslCertificateRequestedCallback(
engineMap, materialManager));
if (keyMaterialProvider != null) {
OpenSslKeyMaterialManager materialManager = new OpenSslKeyMaterialManager(keyMaterialProvider);
SSLContext.setCertRequestedCallback(ctx, new OpenSslCertificateRequestedCallback(
engineMap, materialManager));
}
}
} catch (Exception e) {
throw new SSLException("failed to set certificate and key", e);
}
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
try {
if (trustCertCollection != null) {
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
} else if (trustManagerFactory == null) {
trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
}
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
// IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
// otherwise the context can never be collected. This is because the JNI code holds
// a global reference to the callbacks.
//
// See https://github.com/netty/netty/issues/5372
// Use this to prevent an error when running on java < 7
if (useExtendedTrustManager(manager)) {
SSLContext.setCertVerifyCallback(ctx,
new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager));
} else {
SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager));
}
} catch (Exception e) {
if (keyMaterialProvider != null) {
keyMaterialProvider.destroy();
}
throw new SSLException("unable to setup trustmanager", e);
}
OpenSslClientSessionContext context = new OpenSslClientSessionContext(thiz, keyMaterialProvider);
keyMaterialProvider = null;
return context;
} finally {
if (keyMaterialProvider != null) {
keyMaterialProvider.destroy();
}
} catch (Exception e) {
throw new SSLException("failed to set certificate and key", e);
}
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
try {
if (trustCertCollection != null) {
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
} else if (trustManagerFactory == null) {
trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
}
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
// IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
// otherwise the context can never be collected. This is because the JNI code holds
// a global reference to the callbacks.
//
// See https://github.com/netty/netty/issues/5372
// Use this to prevent an error when running on java < 7
if (useExtendedTrustManager(manager)) {
SSLContext.setCertVerifyCallback(ctx,
new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager));
} else {
SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager));
}
} catch (Exception e) {
throw new SSLException("unable to setup trustmanager", e);
}
return new OpenSslClientSessionContext(thiz);
}
// No cache is currently supported for client side mode.
static final class OpenSslClientSessionContext extends OpenSslSessionContext {
OpenSslClientSessionContext(ReferenceCountedOpenSslContext context) {
super(context);
OpenSslClientSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
super(context, provider);
}
@Override
@ -232,7 +241,8 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
}
@Override
public KeyMaterial requested(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals) {
public void requested(
long ssl, long certOut, long keyOut, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals) {
final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
try {
final Set<String> keyTypesSet = supportedClientKeyTypes(keyTypeBytes);
@ -246,13 +256,12 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
}
}
return keyManagerHolder.keyMaterial(engine, keyTypes, issuers);
keyManagerHolder.setKeyMaterialClientSide(engine, certOut, keyOut, keyTypes, issuers);
} catch (Throwable cause) {
logger.debug("request of key failed", cause);
SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem");
e.initCause(cause);
engine.handshakeException = e;
return null;
}
}

View File

@ -49,11 +49,11 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
@ -486,6 +486,7 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
SSLContext.free(ctx);
ctx = 0;
sessionContext().destroy();
}
} finally {
writerLock.unlock();
@ -566,10 +567,6 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager;
}
static boolean useExtendedKeyManager(X509KeyManager keyManager) {
return PlatformDependent.javaVersion() >= 7 && keyManager instanceof X509ExtendedKeyManager;
}
@Override
public final int refCnt() {
return refCnt.refCnt();
@ -819,4 +816,19 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
buffer.release();
}
}
/**
* Returns the {@link OpenSslKeyMaterialProvider} that should be used for OpenSSL. Depending on the given
* {@link KeyManagerFactory} this may cache the {@link OpenSslKeyMaterial} for better performance if it can
* ensure that the same material is always returned for the same alias.
*/
static OpenSslKeyMaterialProvider providerFor(KeyManagerFactory factory, String password) {
X509KeyManager keyManager = chooseX509KeyManager(factory.getKeyManagers());
if (factory instanceof OpenSslCachingX509KeyManagerFactory) {
// The user explicit used OpenSslCachingX509KeyManagerFactory which signals us that its fine to cache.
return new OpenSslCachingKeyMaterialProvider(keyManager, password);
}
// We can not be sure if the material may change at runtime so we will not cache it.
return new OpenSslKeyMaterialProvider(keyManager, password);
}
}

View File

@ -1567,7 +1567,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
if (!certificateSet && keyMaterialManager != null) {
certificateSet = true;
keyMaterialManager.setKeyMaterial(this);
keyMaterialManager.setKeyMaterialServerSide(this);
}
int code = SSL.doHandshake(ssl);

View File

@ -29,9 +29,7 @@ import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
@ -107,87 +105,97 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
String keyPassword, KeyManagerFactory keyManagerFactory)
throws SSLException {
ServerContext result = new ServerContext();
OpenSslKeyMaterialProvider keyMaterialProvider = null;
try {
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
if (!OpenSsl.useKeyManagerFactory()) {
if (keyManagerFactory != null) {
throw new IllegalArgumentException(
"KeyManagerFactory not supported");
}
checkNotNull(keyCertChain, "keyCertChain");
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
} else {
// 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) {
keyManagerFactory = buildKeyManagerFactory(
keyCertChain, key, keyPassword, keyManagerFactory);
}
X509KeyManager keyManager = chooseX509KeyManager(keyManagerFactory.getKeyManagers());
result.keyMaterialManager = useExtendedKeyManager(keyManager) ?
new OpenSslExtendedKeyMaterialManager(
(X509ExtendedKeyManager) keyManager, keyPassword) :
new OpenSslKeyMaterialManager(keyManager, keyPassword);
}
} catch (Exception e) {
throw new SSLException("failed to set certificate and key", e);
}
try {
if (trustCertCollection != null) {
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
} else if (trustManagerFactory == null) {
// Mimic the way SSLContext.getInstance(KeyManager[], null, null) works
trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
}
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
// IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
// otherwise the context can never be collected. This is because the JNI code holds
// a global reference to the callbacks.
//
// See https://github.com/netty/netty/issues/5372
// Use this to prevent an error when running on java < 7
if (useExtendedTrustManager(manager)) {
SSLContext.setCertVerifyCallback(ctx,
new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager));
} else {
SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager));
}
X509Certificate[] issuers = manager.getAcceptedIssuers();
if (issuers != null && issuers.length > 0) {
long bio = 0;
try {
bio = toBIO(ByteBufAllocator.DEFAULT, issuers);
if (!SSLContext.setCACertificateBio(ctx, bio)) {
throw new SSLException("unable to setup accepted issuers for trustmanager " + manager);
try {
SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
if (!OpenSsl.useKeyManagerFactory()) {
if (keyManagerFactory != null) {
throw new IllegalArgumentException(
"KeyManagerFactory not supported");
}
} finally {
freeBio(bio);
checkNotNull(keyCertChain, "keyCertChain");
setKeyMaterial(ctx, keyCertChain, key, keyPassword);
} else {
// 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);
}
result.keyMaterialManager = new OpenSslKeyMaterialManager(keyMaterialProvider);
}
} catch (Exception e) {
throw new SSLException("failed to set certificate and key", e);
}
try {
if (trustCertCollection != null) {
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
} else if (trustManagerFactory == null) {
// Mimic the way SSLContext.getInstance(KeyManager[], null, null) works
trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
}
if (PlatformDependent.javaVersion() >= 8) {
// Only do on Java8+ as SNIMatcher is not supported in earlier releases.
// IMPORTANT: The callbacks set for hostname matching must be static to prevent memory leak as
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
// IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
// otherwise the context can never be collected. This is because the JNI code holds
// a global reference to the matcher.
SSLContext.setSniHostnameMatcher(ctx, new OpenSslSniHostnameMatcher(engineMap));
}
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException("unable to setup trustmanager", e);
}
// a global reference to the callbacks.
//
// See https://github.com/netty/netty/issues/5372
result.sessionContext = new OpenSslServerSessionContext(thiz);
result.sessionContext.setSessionIdContext(ID);
return result;
// Use this to prevent an error when running on java < 7
if (useExtendedTrustManager(manager)) {
SSLContext.setCertVerifyCallback(ctx,
new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager));
} else {
SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager));
}
X509Certificate[] issuers = manager.getAcceptedIssuers();
if (issuers != null && issuers.length > 0) {
long bio = 0;
try {
bio = toBIO(ByteBufAllocator.DEFAULT, issuers);
if (!SSLContext.setCACertificateBio(ctx, bio)) {
throw new SSLException("unable to setup accepted issuers for trustmanager " + manager);
}
} finally {
freeBio(bio);
}
}
if (PlatformDependent.javaVersion() >= 8) {
// Only do on Java8+ as SNIMatcher is not supported in earlier releases.
// IMPORTANT: The callbacks set for hostname matching must be static to prevent memory leak as
// otherwise the context can never be collected. This is because the JNI code holds
// a global reference to the matcher.
SSLContext.setSniHostnameMatcher(ctx, new OpenSslSniHostnameMatcher(engineMap));
}
} catch (SSLException e) {
throw e;
} catch (Exception e) {
throw new SSLException("unable to setup trustmanager", e);
}
result.sessionContext = new OpenSslServerSessionContext(thiz, keyMaterialProvider);
result.sessionContext.setSessionIdContext(ID);
keyMaterialProvider = null;
return result;
} finally {
if (keyMaterialProvider != null) {
keyMaterialProvider.destroy();
}
}
}
private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier {

View File

@ -121,6 +121,9 @@ 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}.
*
* @param keyManagerFactory non-{@code null} factory for server's private key
* @see #keyManager(KeyManagerFactory)
*/
@ -335,6 +338,9 @@ public final class SslContextBuilder {
* if the used openssl version is 1.0.1+. You can check if your openssl version supports using a
* {@link KeyManagerFactory} by calling {@link OpenSsl#supportsKeyManagerFactory()}. If this is not the case
* 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}.
*/
public SslContextBuilder keyManager(KeyManagerFactory keyManagerFactory) {
if (forServer) {

View File

@ -0,0 +1,77 @@
/*
* 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.UnpooledByteBufAllocator;
import org.junit.Assert;
import org.junit.Test;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.X509KeyManager;
import static org.junit.Assert.*;
public class OpenSslCachingKeyMaterialProviderTest extends OpenSslKeyMaterialProviderTest {
@Override
protected KeyManagerFactory newKeyManagerFactory() throws Exception {
return new OpenSslCachingX509KeyManagerFactory(super.newKeyManagerFactory());
}
@Override
protected OpenSslKeyMaterialProvider newMaterialProvider(X509KeyManager manager, String password) {
return new OpenSslCachingKeyMaterialProvider(manager, password);
}
@Override
protected void assertRelease(OpenSslKeyMaterial material) {
Assert.assertFalse(material.release());
}
@Override
protected Class<? extends OpenSslKeyMaterialProvider> providerClass() {
return OpenSslCachingKeyMaterialProvider.class;
}
@Test
public void testMaterialCached() throws Exception {
X509KeyManager manager = ReferenceCountedOpenSslContext.chooseX509KeyManager(
newKeyManagerFactory().getKeyManagers());
OpenSslKeyMaterialProvider provider = newMaterialProvider(manager, PASSWORD);
OpenSslKeyMaterial material = provider.chooseKeyMaterial(UnpooledByteBufAllocator.DEFAULT, EXISTING_ALIAS);
assertNotNull(material);
assertNotEquals(0, material.certificateChainAddress());
assertNotEquals(0, material.privateKeyAddress());
assertEquals(2, material.refCnt());
OpenSslKeyMaterial material2 = provider.chooseKeyMaterial(UnpooledByteBufAllocator.DEFAULT, EXISTING_ALIAS);
assertNotNull(material2);
assertEquals(material.certificateChainAddress(), material2.certificateChainAddress());
assertEquals(material.privateKeyAddress(), material2.privateKeyAddress());
assertEquals(3, material.refCnt());
assertEquals(3, material2.refCnt());
assertFalse(material.release());
assertFalse(material2.release());
// After this the material should have been released.
provider.destroy();
assertEquals(0, material.refCnt());
assertEquals(0, material2.refCnt());
}
}

View File

@ -0,0 +1,89 @@
/*
* 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.UnpooledByteBufAllocator;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.X509KeyManager;
import java.security.KeyStore;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
public class OpenSslKeyMaterialProviderTest {
static final String PASSWORD = "example";
static final String EXISTING_ALIAS = "1";
private static final String NON_EXISTING_ALIAS = "nonexisting";
@BeforeClass
public static void checkOpenSsl() {
assumeTrue(OpenSsl.isAvailable());
}
protected KeyManagerFactory newKeyManagerFactory() throws Exception {
char[] password = PASSWORD.toCharArray();
final KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(getClass().getResourceAsStream("mutual_auth_server.p12"), password);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, password);
return kmf;
}
protected OpenSslKeyMaterialProvider newMaterialProvider(X509KeyManager manager, String password) {
return new OpenSslKeyMaterialProvider(manager, password);
}
protected Class<? extends OpenSslKeyMaterialProvider> providerClass() {
return OpenSslKeyMaterialProvider.class;
}
protected void assertRelease(OpenSslKeyMaterial material) {
assertTrue(material.release());
}
@Test
public void testChooseKeyMaterial() throws Exception {
X509KeyManager manager = ReferenceCountedOpenSslContext.chooseX509KeyManager(
newKeyManagerFactory().getKeyManagers());
OpenSslKeyMaterialProvider provider = newMaterialProvider(manager, PASSWORD);
OpenSslKeyMaterial nonExistingMaterial = provider.chooseKeyMaterial(
UnpooledByteBufAllocator.DEFAULT, NON_EXISTING_ALIAS);
assertNull(nonExistingMaterial);
OpenSslKeyMaterial material = provider.chooseKeyMaterial(UnpooledByteBufAllocator.DEFAULT, EXISTING_ALIAS);
assertNotNull(material);
assertNotEquals(0, material.certificateChainAddress());
assertNotEquals(0, material.privateKeyAddress());
assertRelease(material);
provider.destroy();
}
@Test
public void testChooseTheCorrectProvider() throws Exception {
OpenSslKeyMaterialProvider provider = ReferenceCountedOpenSslContext.providerFor(
newKeyManagerFactory(), PASSWORD);
assertEquals(providerClass(), provider.getClass());
provider.destroy();
}
}

View File

@ -221,7 +221,7 @@
<!-- Fedora-"like" systems. This is currently only used for the netty-tcnative dependency -->
<os.detection.classifierWithLikes>fedora</os.detection.classifierWithLikes>
<tcnative.artifactId>netty-tcnative</tcnative.artifactId>
<tcnative.version>2.0.10.Final</tcnative.version>
<tcnative.version>2.0.11.Final</tcnative.version>
<tcnative.classifier>${os.detected.classifier}</tcnative.classifier>
<conscrypt.groupId>org.conscrypt</conscrypt.groupId>
<conscrypt.artifactId>conscrypt-openjdk-uber</conscrypt.artifactId>