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:
parent
a3e8c86741
commit
4079189f6b
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
4
pom.xml
4
pom.xml
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user