简体   繁体   English

Netty客户端在需要相互认证的SSL握手期间不发送客户端证书

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

I'm new to Netty and I try to write an echo server and client that uses mutual authentication. 我是Netty的新手,我尝试编写使用双向身份验证的echo服务器和客户端。 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 网络版本4.1.0.CR1
  • Valid keystores, truststores and CRL for download on server 有效的密钥库,信任库和CRL可在服务器上下载
  • A complete implementation of echo server and client using JSSE directly (that is working as expected) 直接使用JSSE的回显服务器和客户端的完整实现(按预期工作)
  • A working implementation of the echo server using Netty (it's working fine when used with the JSSE based client) 使用Netty的echo服务器的有效实现(与基于JSSE的客户端一起使用时效果很好)
  • A client based on Netty that does not send a client certificate 基于Netty的客户端不发送客户端证书

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: 还有一个返回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: VM参数:

-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. 我非常有信心,我的错误一定是在Netty客户端代码中,因为仅使用JSSE时系统运行良好。 Any help is highly appreciated! 任何帮助深表感谢!

Cheers, Andy 干杯,安迪

OK, I've got it to work. 好,我已经开始工作了。 It was actually my client code that was wrong (the code was based on the secure chat example that comes with Netty). 实际上是我的客户代码错了(该代码基于Netty附带的安全聊天示例)。 So I changed it to the version used in the echo example: 因此,我将其更改为回显示例中使用的版本:

EchoClientHandler: 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));
}   

and the EchoClient: 和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();
}   

The previous code just disconnected too early, so that the handshake never completed. 先前的代码断开时间太早,因此握手从未完成。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM