title: Netty client does not send client certificate during SSL handshake that requires mutual authentication
body: <p>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!</p>
  
  <p>That is what I have:</p>
  
  <ul>
  <li>Netty version 4.1.0.CR1</li>
  <li>Valid keystores, truststores and CRL for download on server</li>
  <li>A complete implementation of echo server and client using JSSE directly (that is working as expected)</li>
  <li>A working implementation of the echo server using Netty (it's working fine when used with the JSSE based client)</li>
  <li>A client based on Netty that does not send a client certificate</li>
  </ul>
  
  <p>Client code:</p>
  
  <p>The channel handler:</p>
  
  <pre><code>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&lt;ByteBuf&gt;
  {
      @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();
      }
  }
  </code></pre>
  
  <p>The channel initialiser:</p>
  
  <pre><code>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&lt;Channel&gt;
  {
      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());
      }
  }
  </code></pre>
  
  <p>The echo client:</p>
  
  <pre><code>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() + " &lt;host&gt; &lt;port&gt;");
          }
          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&lt;Channel&gt; 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();
          }
      }
  }
  </code></pre>
  
  <p>And a utility class that returns an SslContext:</p>
  
  <pre><code>...
  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;
      }
  ...
  </code></pre>
  
  <p>VM arguments:</p>
  
  <pre><code>-Djavax.net.debug=all -Djava.security.debug="certpath crl" -Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true
  </code></pre>
  
  <p>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!</p>
  
  <p>Cheers,
  Andy</p>