当前位置: 首页 > 知识库问答 >
问题:

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

彭鸿哲
2023-03-14

我是Netty的新手,我尝试编写一个使用相互身份验证的echo服务器和客户端。不幸的是,它无法工作,客户端没有发送其客户端证书,服务器按预期断开连接。下面是我到目前为止所做的工作和客户端代码的概述--它可能包含一些bug或者我错过了一些重要的东西。谢谢你经历了这一切!

这就是我所拥有的:

  • Netty版本4.1.0.cr1
  • 可在服务器上下载的有效密钥存储库、信任存储库和CRL
  • 直接使用JSSE的echo服务器和客户端的完整实现(按预期工作)
  • 使用Netty的echo服务器的工作实现(与基于JSSE的客户机一起使用时效果很好)
  • 基于Netty的不发送客户端证书的客户端

通道处理程序:

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();
    }
}

通道初始化程序:

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());
    }
}

echo客户端:

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();
        }
    }
}

和返回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;
    }
...
-Djavax.net.debug=all -Djava.security.debug="certpath crl" -Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true

共有1个答案

范鸿
2023-03-14

好吧,我已经开始工作了。实际上是我的客户机代码错了(该代码基于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();
}   
 类似资料:
  • 问题内容: 我正在尝试连接到安全的Web服务。 即使我的密钥库和信任库已正确设置,我也遇到了握手故障。 经过几天的挫败,无休止的谷歌搜索并询问周围的所有人,我发现唯一的问题是java选择在握手期间不将客户端证书发送到服务器。 特别: 服务器请求了客户端证书(CN = RootCA)-即“给我一个由根CA签名的证书” Java查看密钥库,只发现我的客户端证书由“ SubCA”签名,该证书又由“ Ro

  • 我不熟悉SSL和证书。我一直在做关于客户端证书认证的研究。我看过这个和wiki。 因此,如果我必须为我的B2B REST服务实现客户端证书身份验证解决方案,我应该执行以下操作 要求客户端生成自己的私钥,并为其公钥生成证书(CA 颁发?)。通过电子邮件或 USB 闪存盘发送该证书。 在服务器端将客户端的公共证书导入信任存储区并启用客户端身份验证 在握手期间,客户端会出示其证书并进行身份验证,因为服务

  • 授权服务器为进行客户端身份验证的目的,为Web应用客户端创建客户端凭据。授权服务器被鼓励考虑比客户端密码更强的客户端身份验证手段。Web应用程序客户端必须确保客户端密码和其他客户端凭据的机密性。 授权不得向本地应用程序或基于用户代理的应用客户端颁发客户端密码或其他客户端凭据用于客户端验证目的。授权服务器可以颁发客户端密码或其他凭据给专门的设备上特定安装的本地应用程序客户端。 当客户端身份验证不可用

  • 在向令牌端点发起请求时,机密客户端或其他被颁发客户端凭据的客户端必须如2.3节所述与授权服务器进行身份验证。客户端身份验证用于: 实施刷新令牌和授权码到它们被颁发给的客户端的绑定。当授权码在不安全通道上向重定向端点传输时,或者 当重定向URI没有被完全注册时,客户端身份验证是关键的。 通过禁用客户端或者改变其凭据从被入侵的客户端恢复,从而防止攻击者滥用被盗的刷新令牌。改变单套客户端凭据显然快于撤销

  • 如果客户端类型是机密的,客户端和授权服务器建立适合于授权服务器的安全性要求的客户端身份验证方法。授权服务器可以接受符合其安全要求的任何形式的客户端身份验证。 机密客户端通常颁发(或建立)一组客户端凭据用于与授权服务器进行身份验证(例如,密码、公/私钥对)。授权服务器可以与公共客户端建立客户端身份验证方法。然而,授权服务器不能依靠公共客户端身份验证达到识别客户端的目的。 客户端在每次请求中不能使用一

  • 有时需要对某些网络资源(如Servlet、JSP等)进行访问权限验证,也就是说,有访问权限的用户才能访问该网络资源。进行访问权限验证的方法很多,但通过HTTP响应消息头的WWW-Authenticate字段进行访问权限的验证应该是众多权限验证方法中比较简单的一个。 通过HTTP响应消息头的WWW-Authenticate字段可以使浏览器出现一个验证对话框,访问者需要在这个对话框中输入用户名和密码,