Java SSL 握手异常 - "unable to find valid certification path"

标签 java ssl netty self-signed truststore

我尝试使用安全 SSL (TLS) 连接和双向 SSL 身份验证在 Java 上制作服务器和客户端应用程序。单向 SSL(无客户端身份验证)运行良好。启用客户端身份验证后,客户端无法进行异常握手:

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

服务器没有任何异常。我在服务器和客户端中使用 Netty。 我为服务器和客户端使用自签名证书。服务器和客户端——现在是一台物理主机。我已经使用此工具在信任库中添加了服务器证书:

https://java-use-examples.googlecode.com/svn/trunk/src/com/aw/ad/util/InstallCert.java

客户端代码。主要。

public class SClientApp {

public static final String HOST = "127.0.0.1";
public static final int PORT = 8888;

public static void main(String[] args) throws Exception {

    System.setProperty("javax.net.ssl.trustStore", "/etc/ssl/certs/java/cacerts");
    System.setProperty("javax.net.ssl.trustStorePassword", "changeit");

    // Configure SSL (TLS)
    File tls_cert = new File("tls/client1.pem");
    SslContext sslCtx = null;
    try {
        sslCtx = SslContext.newClientContext(tls_cert);
    } catch (SSLException e) {
        e.printStackTrace();
    }

    EventLoopGroup group = new NioEventLoopGroup();

    try {
        Bootstrap b = new Bootstrap();
        b.group(group)
                .channel(NioSocketChannel.class)
                .handler(new SClientInitializer(sslCtx));

        // Start the connection attempt.
        Channel ch = b.connect(HOST, PORT).sync().channel();

        ...

    } finally {
        // The connection is closed automatically on shutdown.
        group.shutdownGracefully();
    }

}
}

客户端代码。 SClientInitializer.

public class SClientInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslCtx;

public SClientInitializer(SslContext sslCtx) {
    this.sslCtx = sslCtx;
}

@Override
protected void initChannel(SocketChannel ch) throws Exception {
    ChannelPipeline pipeline = ch.pipeline();

    SSLEngine ssl_engine = sslCtx.newEngine(ch.alloc(), SClientApp.HOST, SClientApp.PORT);
    ssl_engine.setUseClientMode(true);
    pipeline.addLast(new SslHandler(ssl_engine));

    // On top of the SSL handler, add the text line codec.
    pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
    pipeline.addLast(new StringDecoder());
    pipeline.addLast(new StringEncoder());

    // and then business logic.
    pipeline.addLast(new SClientHandler());
}
}

服务器代码。主要。

public class ServerApp {

static final int PORT = Integer.valueOf(Params.get(Const.SERVER_PORT));

public static void main(String[] args) {

    System.setProperty("javax.net.ssl.trustStore", "/etc/ssl/certs/java/cacerts");
    System.setProperty("javax.net.ssl.trustStorePassword", "changeit");

    // Configure SSL (TLS)
    File tls_cert = new File("tls/server.pem"); // SSL-cert
    File tls_key  = new File("tls/server.key.pkcs8"); // Private key
    SslContext sslCtx = null;
    try {
        sslCtx = SslContext.newServerContext(tls_cert, tls_key);
    } catch (SSLException e) {
        e.printStackTrace();
    }

    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup(2);

    try {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ServerNetInitializer(sslCtx));

        ChannelFuture f = null;
        try {
            f = b.bind(PORT).sync();
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    } finally {
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}
}

服务器代码。初始化程序。

public class ServerNetInitializer extends ChannelInitializer<SocketChannel> {

private final SslContext sslCtx;

public ServerNetInitializer(SslContext sslCtx) {
    this.sslCtx = sslCtx;
}

@Override
protected void initChannel(SocketChannel ch) {

    ChannelPipeline pipeline = ch.pipeline();

    SSLEngine ssl_engine = sslCtx.newEngine(ch.alloc());
    ssl_engine.setUseClientMode(false);
    ssl_engine.setNeedClientAuth(true);
    pipeline.addLast(new SslHandler(ssl_engine));

    // On top of the SSL handler, add the text line codec.
    pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
    pipeline.addLast(new StringDecoder());
    pipeline.addLast(new StringEncoder());

    // and then business logic.
    pipeline.addLast(new ServerNetHandler());
}
}

更新 1。

类 JdkSslClientContext 和 JdkSslServerContext 帮助我。

在服务器端:

sslCtx = new JdkSslServerContext(client_tls_cert, null,
                server_tls_cert, server_tls_key, "", null,
                null, IdentityCipherSuiteFilter.INSTANCE, (ApplicationProtocolConfig) null, 0, 0);

在客户端:

sslCtx = new JdkSslClientContext(server_tls_cert,null,client_tls_cert,client_tls_key,"", null, null,IdentityCipherSuiteFilter.INSTANCE,(ApplicationProtocolConfig) null,0,0);

这里的代码示例: https://github.com/netty/netty/blob/master/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java

更新 2

在服务器端最好使用 TrustManagerFactory 而不是客户端证书的文件对象,因为您可能有很多客户端:

KeyStore ts = null;
    ts = KeyStore.getInstance("JKS");
    ts.load(new FileInputStream(System.getProperty("javax.net.ssl.trustStore")),
            System.getProperty("javax.net.ssl.trustStorePassword").toCharArray());

    // set up trust manager factory to use our trust store
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(ts);

    SslContext sslCtx = null;
    try {
        sslCtx = new JdkSslServerContext(null, tmf,
                server_tls_cert, server_tls_key, "", null,
                null, IdentityCipherSuiteFilter.INSTANCE, (ApplicationProtocolConfig) null, 0, 0);

    } catch (SSLException e) {
        log.error("Making ssl context for server - Exception: " + e.toString());
        e.printStackTrace();
    }

最佳答案

由于 JdkSslClientContext 已弃用,请使用 io.grpc.netty.GrpcSslContexts 创建一个 io.netty.handler.ssl.SslContextBuilder

示例(无相互身份验证):

客户端

InputStream trustCertCollection = new FileInputStream("certs/ca.crt");
SslContextBuilder builder = GrpcSslContexts.forClient();
builder.trustManager(trustCertCollection);
SslContext sslContext = builder.build();

服务器

InputStream certChain = new FileInputStream("certs/server.crt")
InputStream privateKey = new FileInputStream("certs/server.pk8");
SslClientContextBuilder sslClientContextBuilder = SslContextBuilder.forServer(certChain, privateKey);
SslContext sslContext = GrpcSslContexts.configure(sslClientContextBuilder).build();

另见官方示例:https://github.com/grpc/grpc-java/tree/2548bcd7c7afbbe4c6651ea96ba2b62aa336e276/examples/example-tls

关于Java SSL 握手异常 - "unable to find valid certification path",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28725317/

相关文章:

node.js - AWS EC2 负载平衡 SSL Node JS - 我哪里出错了

Java netty AbstractChannelHandlerContext invokeExceptionCaught

java - ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter是否影响netty4中编码器和解码器的运行顺序?

java - 如何匹配两个并行数组的索引?

java - 写入文本文件而不用 Java 覆盖

java - 如何使用 netty 库创建 ssl serversocket

java - 限制 Netty 上每个 IP 的连接数

java - 如何在传递到节点之前替换 Selenium 网格中的功能值

java - Java 中的字符编码不起作用

python - 如何通过fiddler2捕获python SSL(HTTPS)连接