Allow to offload / customize key signing operations when using BoringSSL. (#8943)

Motivation:

BoringSSL allows to customize the way how key signing is done an even offload it from the IO thread. We should provide a way to plugin an own implementation when BoringSSL is used.

Modifications:

- Introduce OpenSslPrivateKeyMethod that can be used by the user to implement custom signing by using ReferenceCountedOpenSslContext.setPrivateKeyMethod(...)
- Introduce static methods to OpenSslKeyManagerFactory which allows to create a KeyManagerFactory which supports to do keyless operations by let the use handle everything in OpenSslPrivateKeyMethod.
- Add testcase which verifies that everything works as expected

Result:

A user is able to customize the way how keys are signed.
This commit is contained in:
Norman Maurer 2019-04-08 20:17:44 +02:00 committed by GitHub
parent a3e8c86741
commit 4079189f6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 573 additions and 18 deletions

View File

@ -0,0 +1,62 @@
/*
* Copyright 2019 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;
import io.netty.internal.tcnative.SSLPrivateKeyMethod;
import io.netty.util.internal.UnstableApi;
import javax.net.ssl.SSLEngine;
/**
* Allow to customize private key signing / decrypting (when using RSA). Only supported when using BoringSSL atm.
*/
@UnstableApi
public interface OpenSslPrivateKeyMethod {
int SSL_SIGN_RSA_PKCS1_SHA1 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA1;
int SSL_SIGN_RSA_PKCS1_SHA256 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA256;
int SSL_SIGN_RSA_PKCS1_SHA384 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA384;
int SSL_SIGN_RSA_PKCS1_SHA512 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA512;
int SSL_SIGN_ECDSA_SHA1 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SHA1;
int SSL_SIGN_ECDSA_SECP256R1_SHA256 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256;
int SSL_SIGN_ECDSA_SECP384R1_SHA384 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP384R1_SHA384;
int SSL_SIGN_ECDSA_SECP521R1_SHA512 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP521R1_SHA512;
int SSL_SIGN_RSA_PSS_RSAE_SHA256 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA256;
int SSL_SIGN_RSA_PSS_RSAE_SHA384 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA384;
int SSL_SIGN_RSA_PSS_RSAE_SHA512 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA512;
int SSL_SIGN_ED25519 = SSLPrivateKeyMethod.SSL_SIGN_ED25519;
int SSL_SIGN_RSA_PKCS1_MD5_SHA1 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_MD5_SHA1;
/**
* Signs the input with the given key and returns the signed bytes.
*
* @param engine the {@link SSLEngine}
* @param signatureAlgorithm the algorithm to use for signing
* @param input the digest itself
* @return the signed data
* @throws Exception thrown if an error is encountered during the signing
*/
byte[] sign(SSLEngine engine, int signatureAlgorithm, byte[] input) throws Exception;
/**
* Decrypts the input with the given key and returns the decrypted bytes.
*
* @param engine the {@link SSLEngine}
* @param input the input which should be decrypted
* @return the decrypted data
* @throws Exception thrown if an error is encountered during the decrypting
*/
byte[] decrypt(SSLEngine engine, byte[] input) throws Exception;
}

View File

@ -252,15 +252,47 @@ public final class OpenSslX509KeyManagerFactory extends KeyManagerFactory {
public static OpenSslX509KeyManagerFactory newEngineBased(X509Certificate[] certificateChain, String password)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
KeyStore store = new OpenSslEngineKeyStore(certificateChain.clone());
KeyStore store = new OpenSslKeyStore(certificateChain.clone(), false);
store.load(null, null);
OpenSslX509KeyManagerFactory factory = new OpenSslX509KeyManagerFactory();
factory.init(store, password == null ? null : password.toCharArray());
return factory;
}
private static final class OpenSslEngineKeyStore extends KeyStore {
private OpenSslEngineKeyStore(final X509Certificate[] certificateChain) {
/**
* See {@link OpenSslX509KeyManagerFactory#newEngineBased(X509Certificate[], String)}.
*/
public static OpenSslX509KeyManagerFactory newKeyless(File chain)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
return newKeyless(SslContext.toX509Certificates(chain));
}
/**
* See {@link OpenSslX509KeyManagerFactory#newEngineBased(X509Certificate[], String)}.
*/
public static OpenSslX509KeyManagerFactory newKeyless(InputStream chain)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
return newKeyless(SslContext.toX509Certificates(chain));
}
/**
* Returns a new initialized {@link OpenSslX509KeyManagerFactory} which will provide its private key by using the
* {@link OpenSslPrivateKeyMethod}.
*/
public static OpenSslX509KeyManagerFactory newKeyless(X509Certificate... certificateChain)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
KeyStore store = new OpenSslKeyStore(certificateChain.clone(), true);
store.load(null, null);
OpenSslX509KeyManagerFactory factory = new OpenSslX509KeyManagerFactory();
factory.init(store, null);
return factory;
}
private static final class OpenSslKeyStore extends KeyStore {
private OpenSslKeyStore(final X509Certificate[] certificateChain, final boolean keyless) {
super(new KeyStoreSpi() {
private final Date creationDate = new Date();
@ -268,15 +300,21 @@ public final class OpenSslX509KeyManagerFactory extends KeyManagerFactory {
@Override
public Key engineGetKey(String alias, char[] password) throws UnrecoverableKeyException {
if (engineContainsAlias(alias)) {
try {
return new OpenSslPrivateKey(SSL.loadPrivateKeyFromEngine(
alias, password == null ? null : new String(password)));
} catch (Exception e) {
UnrecoverableKeyException keyException =
new UnrecoverableKeyException("Unable to load key from engine");
keyException.initCause(e);
throw keyException;
final long privateKeyAddress;
if (keyless) {
privateKeyAddress = 0;
} else {
try {
privateKeyAddress = SSL.loadPrivateKeyFromEngine(
alias, password == null ? null : new String(password));
} catch (Exception e) {
UnrecoverableKeyException keyException =
new UnrecoverableKeyException("Unable to load key from engine");
keyException.initCause(e);
throw keyException;
}
}
return new OpenSslPrivateKey(privateKeyAddress);
}
return null;
}

View File

@ -20,14 +20,17 @@ import io.netty.buffer.ByteBufAllocator;
import io.netty.internal.tcnative.CertificateVerifier;
import io.netty.internal.tcnative.SSL;
import io.netty.internal.tcnative.SSLContext;
import io.netty.internal.tcnative.SSLPrivateKeyMethod;
import io.netty.util.AbstractReferenceCounted;
import io.netty.util.ReferenceCounted;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.ResourceLeakTracker;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -86,7 +89,7 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
2048));
}
});
private static final boolean USE_TASKS =
static final boolean USE_TASKS =
SystemPropertyUtil.getBoolean("io.netty.handler.ssl.openssl.useTasks", false);
private static final Integer DH_KEY_LENGTH;
private static final ResourceLeakDetector<ReferenceCountedOpenSslContext> leakDetector =
@ -517,6 +520,26 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
}
}
/**
* Set the {@link OpenSslPrivateKeyMethod} to use. This allows to offload private-key operations
* if needed.
*
* This method is currently only supported when {@code BoringSSL} is used.
*
* @param method method to use.
*/
@UnstableApi
public final void setPrivateKeyMethod(OpenSslPrivateKeyMethod method) {
ObjectUtil.checkNotNull(method, "method");
Lock writerLock = ctxLock.writeLock();
writerLock.lock();
try {
SSLContext.setPrivateKeyMethod(ctx, new PrivateKeyMethod(engineMap, method));
} finally {
writerLock.unlock();
}
}
// IMPORTANT: This method must only be called from either the constructor or the finalizer as a user MUST never
// get access to an OpenSslSessionContext after this method was called to prevent the user from
// producing a segfault.
@ -882,4 +905,45 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
// We can not be sure if the material may change at runtime so we will not cache it.
return new OpenSslKeyMaterialProvider(keyManager, password);
}
private static final class PrivateKeyMethod implements SSLPrivateKeyMethod {
private final OpenSslEngineMap engineMap;
private final OpenSslPrivateKeyMethod keyMethod;
PrivateKeyMethod(OpenSslEngineMap engineMap, OpenSslPrivateKeyMethod keyMethod) {
this.engineMap = engineMap;
this.keyMethod = keyMethod;
}
private ReferenceCountedOpenSslEngine retrieveEngine(long ssl) throws SSLException {
ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
if (engine == null) {
throw new SSLException("Could not find a " +
StringUtil.simpleClassName(ReferenceCountedOpenSslEngine.class) + " for sslPointer " + ssl);
}
return engine;
}
@Override
public byte[] sign(long ssl, int signatureAlgorithm, byte[] digest) throws Exception {
ReferenceCountedOpenSslEngine engine = retrieveEngine(ssl);
try {
return keyMethod.sign(engine, signatureAlgorithm, digest);
} catch (Exception e) {
engine.initHandshakeException(e);
throw e;
}
}
@Override
public byte[] decrypt(long ssl, byte[] input) throws Exception {
ReferenceCountedOpenSslEngine engine = retrieveEngine(ssl);
try {
return keyMethod.decrypt(engine, input);
} catch (Exception e) {
engine.initHandshakeException(e);
throw e;
}
}
}
}

View File

@ -885,7 +885,9 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
// [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html
return newResult(BUFFER_OVERFLOW, status, bytesConsumed, bytesProduced);
} else if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP ||
sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY) {
sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY ||
sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) {
return newResult(NEED_TASK, bytesConsumed, bytesProduced);
} else {
// Everything else is considered as error
@ -1181,8 +1183,9 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
}
return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status,
bytesConsumed, bytesProduced);
} else if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP
|| sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY) {
} else if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP ||
sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY ||
sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) {
return newResult(isInboundDone() ? CLOSED : OK,
NEED_TASK, bytesConsumed, bytesProduced);
} else {
@ -1737,7 +1740,9 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
return pendingStatus(SSL.bioLengthNonApplication(networkBIO));
}
if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY) {
if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP ||
sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY ||
sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) {
return NEED_TASK;
}

View File

@ -0,0 +1,386 @@
/*
* Copyright 2019 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version
* 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.netty.handler.ssl;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalServerChannel;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Promise;
import org.hamcrest.Matchers;
import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLHandshakeException;
import java.net.SocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class)
public class OpenSslPrivateKeyMethodTest {
private static final String RFC_CIPHER_NAME = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
private static EventLoopGroup GROUP;
private static SelfSignedCertificate CERT;
private static ExecutorService EXECUTOR;
@Parameters(name = "{index}: delegate = {0}")
public static Collection<Object[]> parameters() {
List<Object[]> dst = new ArrayList<Object[]>();
dst.add(new Object[] { true });
dst.add(new Object[] { false });
return dst;
}
@BeforeClass
public static void init() throws Exception {
Assume.assumeTrue(OpenSsl.isBoringSSL());
// Check if the cipher is supported at all which may not be the case for various JDK versions and OpenSSL API
// implementations.
assumeCipherAvailable(SslProvider.OPENSSL);
assumeCipherAvailable(SslProvider.JDK);
GROUP = new DefaultEventLoopGroup();
CERT = new SelfSignedCertificate();
EXECUTOR = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new DelegateThread(r);
}
});
}
@AfterClass
public static void destroy() {
GROUP.shutdownGracefully();
CERT.delete();
EXECUTOR.shutdown();
}
private final boolean delegate;
public OpenSslPrivateKeyMethodTest(boolean delegate) {
this.delegate = delegate;
}
private static void assumeCipherAvailable(SslProvider provider) throws NoSuchAlgorithmException {
boolean cipherSupported = false;
if (provider == SslProvider.JDK) {
SSLEngine engine = SSLContext.getDefault().createSSLEngine();
for (String c: engine.getSupportedCipherSuites()) {
if (RFC_CIPHER_NAME.equals(c)) {
cipherSupported = true;
break;
}
}
} else {
cipherSupported = OpenSsl.isCipherSuiteAvailable(RFC_CIPHER_NAME);
}
Assume.assumeTrue("Unsupported cipher: " + RFC_CIPHER_NAME, cipherSupported);
}
private static SslHandler newSslHandler(SslContext sslCtx, ByteBufAllocator allocator, Executor executor) {
if (executor == null) {
return sslCtx.newHandler(allocator);
} else {
return sslCtx.newHandler(allocator, executor);
}
}
private SslContext buildServerContext(OpenSslPrivateKeyMethod method) throws Exception {
List<String> ciphers = Collections.singletonList(RFC_CIPHER_NAME);
final KeyManagerFactory kmf = OpenSslX509KeyManagerFactory.newKeyless(CERT.cert());
final SslContext sslServerContext = SslContextBuilder.forServer(kmf)
.sslProvider(SslProvider.OPENSSL)
.ciphers(ciphers)
// As this is not a TLSv1.3 cipher we should ensure we talk something else.
.protocols(SslUtils.PROTOCOL_TLS_V1_2)
.build();
((OpenSslContext) sslServerContext).setPrivateKeyMethod(method);
return sslServerContext;
}
private SslContext buildClientContext() throws Exception {
return SslContextBuilder.forClient()
.sslProvider(SslProvider.JDK)
.ciphers(Collections.singletonList(RFC_CIPHER_NAME))
// As this is not a TLSv1.3 cipher we should ensure we talk something else.
.protocols(SslUtils.PROTOCOL_TLS_V1_2)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
}
private Executor delegateExecutor() {
return delegate ? EXECUTOR : null;
}
private void assertThread() {
if (delegate && OpenSslContext.USE_TASKS) {
assertEquals(DelegateThread.class, Thread.currentThread().getClass());
} else {
assertNotEquals(DelegateThread.class, Thread.currentThread().getClass());
}
}
@Test
public void testPrivateKeyMethod() throws Exception {
final AtomicBoolean signCalled = new AtomicBoolean();
final SslContext sslServerContext = buildServerContext(new OpenSslPrivateKeyMethod() {
@Override
public byte[] sign(SSLEngine engine, int signatureAlgorithm, byte[] input) throws Exception {
signCalled.set(true);
assertThread();
assertEquals(CERT.cert().getPublicKey(),
engine.getSession().getLocalCertificates()[0].getPublicKey());
// Delegate signing to Java implementation.
final Signature signature;
// Depending on the Java version it will pick one or the other.
if (signatureAlgorithm == OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA256) {
signature = Signature.getInstance("SHA256withRSA");
} else if (signatureAlgorithm == OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA256) {
signature = Signature.getInstance("RSASSA-PSS");
signature.setParameter(new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256,
32, 1));
} else {
throw new AssertionError("Unexpected signature algorithm " + signatureAlgorithm);
}
signature.initSign(CERT.key());
signature.update(input);
return signature.sign();
}
@Override
public byte[] decrypt(SSLEngine engine, byte[] input) {
throw new UnsupportedOperationException();
}
});
final SslContext sslClientContext = buildClientContext();
try {
try {
final Promise<Object> serverPromise = GROUP.next().newPromise();
final Promise<Object> clientPromise = GROUP.next().newPromise();
ChannelHandler serverHandler = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(newSslHandler(sslServerContext, ch.alloc(), delegateExecutor()));
pipeline.addLast(new SimpleChannelInboundHandler<Object>() {
@Override
public void channelInactive(ChannelHandlerContext ctx) {
serverPromise.cancel(true);
ctx.fireChannelInactive();
}
@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) {
if (serverPromise.trySuccess(null)) {
ctx.writeAndFlush(Unpooled.wrappedBuffer(new byte[] {'P', 'O', 'N', 'G'}));
}
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (!serverPromise.tryFailure(cause)) {
ctx.fireExceptionCaught(cause);
}
}
});
}
};
LocalAddress address = new LocalAddress("test-" + SslProvider.OPENSSL
+ '-' + SslProvider.JDK + '-' + RFC_CIPHER_NAME + '-' + delegate);
Channel server = server(address, serverHandler);
try {
ChannelHandler clientHandler = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(newSslHandler(sslClientContext, ch.alloc(), delegateExecutor()));
pipeline.addLast(new SimpleChannelInboundHandler<Object>() {
@Override
public void channelInactive(ChannelHandlerContext ctx) {
clientPromise.cancel(true);
ctx.fireChannelInactive();
}
@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) {
clientPromise.trySuccess(null);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (!clientPromise.tryFailure(cause)) {
ctx.fireExceptionCaught(cause);
}
}
});
}
};
Channel client = client(server, clientHandler);
try {
client.writeAndFlush(Unpooled.wrappedBuffer(new byte[] {'P', 'I', 'N', 'G'}))
.syncUninterruptibly();
assertTrue("client timeout", clientPromise.await(5L, TimeUnit.SECONDS));
assertTrue("server timeout", serverPromise.await(5L, TimeUnit.SECONDS));
clientPromise.sync();
serverPromise.sync();
assertTrue(signCalled.get());
} finally {
client.close().sync();
}
} finally {
server.close().sync();
}
} finally {
ReferenceCountUtil.release(sslClientContext);
}
} finally {
ReferenceCountUtil.release(sslServerContext);
}
}
@Test
public void testPrivateKeyMethodFails() throws Exception {
final SslContext sslServerContext = buildServerContext(new OpenSslPrivateKeyMethod() {
@Override
public byte[] sign(SSLEngine engine, int signatureAlgorithm, byte[] input) throws Exception {
assertThread();
throw new SignatureException();
}
@Override
public byte[] decrypt(SSLEngine engine, byte[] input) {
throw new UnsupportedOperationException();
}
});
final SslContext sslClientContext = buildClientContext();
SslHandler serverSslHandler = newSslHandler(
sslServerContext, UnpooledByteBufAllocator.DEFAULT, delegateExecutor());
SslHandler clientSslHandler = newSslHandler(
sslClientContext, UnpooledByteBufAllocator.DEFAULT, delegateExecutor());
try {
try {
LocalAddress address = new LocalAddress("test-" + SslProvider.OPENSSL
+ '-' + SslProvider.JDK + '-' + RFC_CIPHER_NAME + '-' + delegate);
Channel server = server(address, serverSslHandler);
try {
Channel client = client(server, clientSslHandler);
try {
Throwable clientCause = clientSslHandler.handshakeFuture().await().cause();
Throwable serverCause = serverSslHandler.handshakeFuture().await().cause();
assertNotNull(clientCause);
assertThat(serverCause, Matchers.instanceOf(SSLHandshakeException.class));
} finally {
client.close().sync();
}
} finally {
server.close().sync();
}
} finally {
ReferenceCountUtil.release(sslClientContext);
}
} finally {
ReferenceCountUtil.release(sslServerContext);
}
}
private static Channel server(LocalAddress address, ChannelHandler handler) throws Exception {
ServerBootstrap bootstrap = new ServerBootstrap()
.channel(LocalServerChannel.class)
.group(GROUP)
.childHandler(handler);
return bootstrap.bind(address).sync().channel();
}
private static Channel client(Channel server, ChannelHandler handler) throws Exception {
SocketAddress remoteAddress = server.localAddress();
Bootstrap bootstrap = new Bootstrap()
.channel(LocalChannel.class)
.group(GROUP)
.handler(handler);
return bootstrap.connect(remoteAddress).sync().channel();
}
private static final class DelegateThread extends Thread {
DelegateThread(Runnable target) {
super(target);
}
}
}

View File

@ -282,8 +282,8 @@
<!-- Configure the os-maven-plugin extension to expand the classifier on -->
<!-- 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.23.Final</tcnative.version>
<tcnative.artifactId>netty-tcnative-boringssl-static</tcnative.artifactId>
<tcnative.version>2.0.24.Final</tcnative.version>
<tcnative.classifier>${os.detected.classifier}</tcnative.classifier>
<conscrypt.groupId>org.conscrypt</conscrypt.groupId>
<conscrypt.artifactId>conscrypt-openjdk-uber</conscrypt.artifactId>