Add support for server-side renegotiation when using OpenSslEngine.
Motivation: JDK SslEngine supports renegotion, so we should at least support it server-side with OpenSslEngine as well. That said OpenSsl does not support sending messages asynchronly while the renegotiation is still in progress, so the application need to ensure there are not writes going on while the renegotiation takes place. See also https://rt.openssl.org/Ticket/Display.html?id=1019 . Modifications: - Add support for renegotiation when OpenSslEngine is used in server mode - Add unit tests. - Upgrade to netty-tcnative 1.1.33.Fork9 Result: Better compatibility with the JDK SSLEngine implementation.
This commit is contained in:
parent
836c9b4844
commit
87062671b8
@ -1042,10 +1042,6 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
@Override
|
||||
public synchronized void beginHandshake() throws SSLException {
|
||||
switch (handshakeState) {
|
||||
case NOT_STARTED:
|
||||
handshakeState = HandshakeState.STARTED_EXPLICITLY;
|
||||
handshake();
|
||||
break;
|
||||
case STARTED_IMPLICITLY:
|
||||
checkEngineClosed();
|
||||
|
||||
@ -1056,11 +1052,39 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
// for renegotiation.
|
||||
|
||||
handshakeState = HandshakeState.STARTED_EXPLICITLY; // Next time this method is invoked by the user,
|
||||
// we should raise an exception.
|
||||
// we should raise an exception.
|
||||
break;
|
||||
case STARTED_EXPLICITLY:
|
||||
// Nothing to do as the handshake is not done yet.
|
||||
break;
|
||||
case FINISHED:
|
||||
case STARTED_EXPLICITLY:
|
||||
throw RENEGOTIATION_UNSUPPORTED;
|
||||
if (clientMode) {
|
||||
// Only supported for server mode at the moment.
|
||||
throw RENEGOTIATION_UNSUPPORTED;
|
||||
}
|
||||
// For renegotiate on the server side we need to issue the following command sequence with openssl:
|
||||
//
|
||||
// SSL_renegotiate(ssl)
|
||||
// SSL_do_handshake(ssl)
|
||||
// ssl->state = SSL_ST_ACCEPT
|
||||
// SSL_do_handshake(ssl)
|
||||
//
|
||||
// Bcause of this we fall-through to call handshake() after setting the state, as this will also take
|
||||
// care of updating the internal OpenSslSession object.
|
||||
//
|
||||
// See also:
|
||||
// https://github.com/apache/httpd/blob/2.4.16/modules/ssl/ssl_engine_kernel.c#L812
|
||||
// http://h71000.www7.hp.com/doc/83final/ba554_90007/ch04s03.html
|
||||
if (SSL.renegotiate(ssl) != 1 || SSL.doHandshake(ssl) != 1) {
|
||||
shutdownWithError("renegotiation failed");
|
||||
}
|
||||
|
||||
SSL.setState(ssl, SSL.SSL_ST_ACCEPT);
|
||||
// fall-through
|
||||
case NOT_STARTED:
|
||||
handshakeState = HandshakeState.STARTED_EXPLICITLY;
|
||||
handshake();
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
@ -1078,6 +1102,9 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
}
|
||||
|
||||
private HandshakeStatus handshake() throws SSLException {
|
||||
if (handshakeState == HandshakeState.FINISHED) {
|
||||
return FINISHED;
|
||||
}
|
||||
checkEngineClosed();
|
||||
int code = SSL.doHandshake(ssl);
|
||||
if (code <= 0) {
|
||||
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2015 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;
|
||||
|
||||
public class JdkSslRenegotiateTest extends RenegotiateTest {
|
||||
|
||||
@Override
|
||||
protected SslProvider serverSslProvider() {
|
||||
return SslProvider.JDK;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2015 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 org.junit.Assume;
|
||||
|
||||
public class OpenSslRenegotiateTest extends RenegotiateTest {
|
||||
|
||||
@Override
|
||||
public void testRenegotiateServer() throws Throwable {
|
||||
Assume.assumeTrue(OpenSsl.isAvailable());
|
||||
super.testRenegotiateServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SslProvider serverSslProvider() {
|
||||
return SslProvider.OPENSSL;
|
||||
}
|
||||
}
|
137
handler/src/test/java/io/netty/handler/ssl/RenegotiateTest.java
Normal file
137
handler/src/test/java/io/netty/handler/ssl/RenegotiateTest.java
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2015 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.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.local.LocalEventLoopGroup;
|
||||
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.Future;
|
||||
import io.netty.util.concurrent.FutureListener;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public abstract class RenegotiateTest {
|
||||
|
||||
@Test(timeout = 5000)
|
||||
public void testRenegotiateServer() throws Throwable {
|
||||
final AtomicReference<Throwable> error = new AtomicReference<Throwable>();
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
SelfSignedCertificate cert = new SelfSignedCertificate();
|
||||
EventLoopGroup group = new LocalEventLoopGroup();
|
||||
try {
|
||||
final SslContext context = SslContextBuilder.forServer(cert.key(), cert.cert())
|
||||
.sslProvider(serverSslProvider()).build();
|
||||
ServerBootstrap sb = new ServerBootstrap();
|
||||
sb.group(group).channel(LocalServerChannel.class)
|
||||
.childHandler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ch.pipeline().addLast(context.newHandler(ch.alloc()));
|
||||
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
|
||||
private boolean renegotiate;
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(
|
||||
final ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (!renegotiate && evt instanceof SslHandshakeCompletionEvent) {
|
||||
SslHandshakeCompletionEvent event = (SslHandshakeCompletionEvent) evt;
|
||||
|
||||
if (event.isSuccess()) {
|
||||
final SslHandler handler = ctx.pipeline().get(SslHandler.class);
|
||||
|
||||
renegotiate = true;
|
||||
handler.renegotiate().addListener(new FutureListener<Channel>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Channel> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
error.compareAndSet(null, future.cause());
|
||||
latch.countDown();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
error.compareAndSet(null, event.cause());
|
||||
latch.countDown();
|
||||
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Channel channel = sb.bind(new LocalAddress("test")).syncUninterruptibly().channel();
|
||||
|
||||
final SslContext clientContext = SslContextBuilder.forClient()
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE).sslProvider(SslProvider.JDK).build();
|
||||
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
bootstrap.group(group).channel(LocalChannel.class)
|
||||
.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ch.pipeline().addLast(clientContext.newHandler(ch.alloc()));
|
||||
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void userEventTriggered(
|
||||
ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof SslHandshakeCompletionEvent) {
|
||||
SslHandshakeCompletionEvent event = (SslHandshakeCompletionEvent) evt;
|
||||
if (!event.isSuccess()) {
|
||||
error.compareAndSet(null, event.cause());
|
||||
ctx.close();
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Channel clientChannel = bootstrap.connect(channel.localAddress()).syncUninterruptibly().channel();
|
||||
latch.await();
|
||||
clientChannel.close().syncUninterruptibly();
|
||||
channel.close().syncUninterruptibly();
|
||||
Throwable cause = error.get();
|
||||
if (cause != null) {
|
||||
throw cause;
|
||||
}
|
||||
} finally {
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract SslProvider serverSslProvider();
|
||||
}
|
Loading…
Reference in New Issue
Block a user