diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingKeyMaterialProvider.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingKeyMaterialProvider.java new file mode 100644 index 0000000000..db8779bdbd --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingKeyMaterialProvider.java @@ -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 cache = new ConcurrentHashMap(); + + 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 iterator = cache.values().iterator(); + while (iterator.hasNext()) { + iterator.next().release(); + iterator.remove(); + } + } while (!cache.isEmpty()); + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingX509KeyManagerFactory.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingX509KeyManagerFactory.java new file mode 100644 index 0000000000..6581d9b732 --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingX509KeyManagerFactory.java @@ -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()); + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java deleted file mode 100644 index 38f6a7f723..0000000000 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java +++ /dev/null @@ -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); - } -} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterial.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterial.java new file mode 100644 index 0000000000..749026807b --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterial.java @@ -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 leakDetector = + ResourceLeakDetectorFactory.instance().newResourceLeakDetector(OpenSslKeyMaterial.class); + private final ResourceLeakTracker 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); + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java index fd2a7ee8b8..f365dee279 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java @@ -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 aliases = new HashSet(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); } } diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialProvider.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialProvider.java new file mode 100644 index 0000000000..9f4d6ee2fb --- /dev/null +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialProvider.java @@ -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. + } +} diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java index 8c92debfec..691ee0b661 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java @@ -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 diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java index 846a968735..9faefb1380 100644 --- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java @@ -34,14 +34,21 @@ public abstract class OpenSslSessionContext implements SSLSessionContext { private static final Enumeration 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 { @Override public boolean hasMoreElements() { diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java index b2135734e9..ea63bad6ac 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java @@ -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 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; } } diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java index 84cf6da0a7..9a1a1c0245 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java @@ -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); + } } diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java index 788a620f91..658bb01558 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java +++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java @@ -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); diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java index 8eb4940c40..b8f0254ef5 100644 --- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java +++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java @@ -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 { diff --git a/handler/src/main/java/io/netty/handler/ssl/SslContextBuilder.java b/handler/src/main/java/io/netty/handler/ssl/SslContextBuilder.java index 2e3c55b9cb..48617505af 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslContextBuilder.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslContextBuilder.java @@ -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) { diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslCachingKeyMaterialProviderTest.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslCachingKeyMaterialProviderTest.java new file mode 100644 index 0000000000..e284da1517 --- /dev/null +++ b/handler/src/test/java/io/netty/handler/ssl/OpenSslCachingKeyMaterialProviderTest.java @@ -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 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()); + } +} diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslKeyMaterialProviderTest.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslKeyMaterialProviderTest.java new file mode 100644 index 0000000000..651db93f08 --- /dev/null +++ b/handler/src/test/java/io/netty/handler/ssl/OpenSslKeyMaterialProviderTest.java @@ -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 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(); + } +} diff --git a/pom.xml b/pom.xml index de59d8e712..a3a1622dcd 100644 --- a/pom.xml +++ b/pom.xml @@ -221,7 +221,7 @@ fedora netty-tcnative - 2.0.10.Final + 2.0.11.Final ${os.detected.classifier} org.conscrypt conscrypt-openjdk-uber