Netty client does not send client certificate during SSL handshake that requires mutual authentication

时间:2016-02-03 02:38:07

标签: ssl netty

I'm new to Netty and I try to write an echo server and client that uses mutual authentication. Unfortunately, it's not working, the client doesn't send its client certificate and the server disconnects as expected. Below an overview of what I've done so far and the client side code - that probably contains some bug or I missed something important. Thanks for going through all this!

That is what I have:

  • Netty version 4.1.0.CR1
  • Valid keystores, truststores and CRL for download on server
  • A complete implementation of echo server and client using JSSE directly (that is working as expected)
  • A working implementation of the echo server using Netty (it's working fine when used with the JSSE based client)
  • A client based on Netty that does not send a client certificate

Client code:

The channel handler:

package info.junius.tutorial.echo.netty.tls;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>
{
    @Override
    public void channelRead0(ChannelHandlerContext ctx, ByteBuf in)
    {
        System.out.println("CLIENT: Received echo from server:\n" + in.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
    {
        cause.printStackTrace();
        ctx.close();
    }
}

The channel initialiser:

package info.junius.tutorial.echo.netty.tls;

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.ssl.SslContext;

public class ClientChannelInitializer extends ChannelInitializer<Channel>
{
    private final SslContext context;
    private final String peerHost;
    private final int peerPort;

    public ClientChannelInitializer(SslContext context, String peerHost, int peerPort)
    {
        this.context = context;
        this.peerHost = peerHost;
        this.peerPort = peerPort;
    }

    @Override
    protected void initChannel(Channel channel) throws Exception
    {
        // Add SSL handler first to encrypt and decrypt everything.
        channel.pipeline().addLast(this.context.newHandler(channel.alloc(), this.peerHost, this.peerPort));
        // and then business logic.
        channel.pipeline().addLast(new EchoClientHandler());
    }
}

The echo client:

package info.junius.tutorial.echo.netty.tls;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class EchoClient
{
    private final String host;
    private final int port;

    public EchoClient(String host, int port)
    {
        super();
        this.host = host;
        this.port = port;
    }

    public static void main(String[] args) throws Exception
    {
        if (args.length != 2)
        {
            System.err.println("Usage: " + EchoClient.class.getSimpleName() + " <host> <port>");
        }
        else
        {
            // Security.addProvider(new BouncyCastleProvider());
            String host = args[0];
            int port = Integer.parseInt(args[1]);
            new EchoClient(host, port).start();
        }
    }

    public void start() throws Exception
    {
        TlsContextUtil tlsContextUtil = new TlsContextUtil();
        ChannelInitializer<Channel> channelInitializer = new ClientChannelInitializer(tlsContextUtil.getClientContext(), this.host, this.port);
        EventLoopGroup group = new NioEventLoopGroup();
        try
        {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class).handler(channelInitializer);
            Channel channel = b.connect(this.host, this.port).sync().channel();
            ChannelFuture writeFuture = channel.writeAndFlush("Hello from netty client!\n");
            // channel.closeFuture().sync();
            writeFuture.sync();
        }
        finally
        {
            group.shutdownGracefully().sync();
        }
    }
}

And a utility class that returns an SslContext:

...
public SslContext getClientContext() throws IOException
    {
        SslContext sslContext = null;
        try
        {
            // truststore
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
            tmf.init(this.getKeystore(TRUSTSTORE));

            // keystore holding client certificate
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX", "SunJSSE");
            kmf.init(this.getKeystore(CLIENT_KEYSTORE), KEYSTORE_PW);

            SslContextBuilder builder = SslContextBuilder.forClient().keyManager(kmf).trustManager(tmf).ciphers(PFS_CIPHERS);

            // build context
            sslContext = builder.build();
        }
        catch (NoSuchAlgorithmException
               | NoSuchProviderException
               | KeyStoreException
               | IllegalStateException
               | UnrecoverableKeyException e)
        {
            throw new IOException("Unable to create client TLS context", e);
        }
        return sslContext;
    }
...

VM arguments:

-Djavax.net.debug=all -Djava.security.debug="certpath crl" -Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true

I'm quite confident that my mistake must be in the Netty client code, because the system works fine when using JSSE only. Any help is highly appreciated!

Cheers, Andy

1 个答案:

答案 0 :(得分:0)

好的,我已经开始工作了。实际上我的客户端代码是错误的(代码是基于Netty附带的安全聊天示例)。所以我将其更改为echo示例中使用的版本:

EchoClientHandler:

@Override
public void channelActive(ChannelHandlerContext ctx)
{
    // When notified that the channel is active send a message.
    System.out.println("CLIENT: Sending request to server...");
    ctx.writeAndFlush(Unpooled.copiedBuffer("Mein Schnitzel ist kaputt!\n", CharsetUtil.UTF_8));
}   

和EchoClient:

try
{
    Bootstrap b = new Bootstrap();
    b.group(group).channel(NioSocketChannel.class).handler(channelInitializer);
    ChannelFuture f = b.connect(this.host, this.port).sync();
    f.channel().closeFuture().sync();
}
finally
{
    group.shutdownGracefully().sync();
}   

之前的代码过早断开,因此握手永远不会完成。