Исключение рукопожатия Java SSL - не удалось найти действительный путь сертификации

Я пытаюсь создавать серверные и клиентские приложения на Java с безопасным соединением SSL (TLS) и двусторонней аутентификацией SSL. Односторонний 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 вместо объекта File клиентского сертификата, потому что у вас может быть много клиентов:

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

person Nik    schedule 25.02.2015    source источник
comment
Эта программа googlecode помещает сертификат сервера либо в jssecacerts, либо в JRE/lib/security/cacerts, что почти (но не совсем) расположение по умолчанию, используемое Java/JSSE для своего хранилища доверенных сертификатов. Однако вы настраиваете явное расположение хранилища доверенных сертификатов, которое совершенно отличается. Вы должны либо поместить сертификат в хранилище доверенных сертификатов по умолчанию и использовать это хранилище доверенных сертификатов по умолчанию, либо поместить сертификат в какое-либо другое хранилище доверенных сертификатов и использовать это другое хранилище доверенных сертификатов.   -  person dave_thompson_085    schedule 26.02.2015
comment
Эта программа создает файл jssecacerts в текущем каталоге. Я заменяю существующий файл /etc/ssl/certs/java/cacerts новым файлом. Разрешения идентичные. Файл $JAVA_HOME/jre/lib/security/cacerts — это ссылка на /etc/ssl/certs/java/cacerts.   -  person Nik    schedule 26.02.2015
comment
Глядя еще раз, отличается ли SslContext.newClientContext() клиента для клиентской аутентификации, также известной как двусторонняя? В частности, может ли это создать контекст, который не использует хранилище доверенных сертификатов по умолчанию, где без клиентской аутентификации он использовал хранилище доверенных сертификатов по умолчанию (или, возможно, глобальный SSLContext по умолчанию)? Поскольку это явно не стандартный javax.net.ssl.SSLContext, я уверен, что это пользовательский класс, который делает примерно то же самое, но здесь детали этого примерно имеют значение.   -  person dave_thompson_085    schedule 27.02.2015
comment
Спасибо за помощь! Я обновил свой пост.   -  person Nik    schedule 28.02.2015
comment
Для обновления 1: если вы имеете в виду netty.io/4.0/api /io/netty/handler/ssl/JdkSslClientContext.html этот ctor (перегрузка) говорит, что он использует только для доверия (?) arg#1 server_tls_cert, который должен содержать сертификаты в PEM, если он ненулевой, поэтому дискуссия о настройке хранилища доверенных сертификатов по умолчанию properties и размещение сертификата сервера в JKS, по-видимому, не имеет значения. Вместо этого, что находится в файле server_tls_cert? Касательно обновления 2: этот ctor говорит, что принимает PEM цепочки во множественном числе, которые могут обрабатывать множество клиентов, хотя подход JKS-TMF имеет смысл, если у вас есть клиентские сертификаты (только?) в ЖКС.   -  person dave_thompson_085    schedule 04.03.2015


Ответы (4)


Поскольку 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

person mjduijn    schedule 01.07.2019

попробуй это:

  • поместите свой сертификат в хранилище ключей с помощью keytool:

keytool -import -alias myAlias-file mycert.crt -keystore mykeystore.jks -storepass changeit

  • добавить системные свойства в ваш код

System.setProperty("javax.net.ssl.keyStore", keyStore); System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword); System.setProperty("javax.net.ssl.trustStore", trustStore); System.setProperty("javax.net.ssl.keyStorePassword", trustStorePassword);

person konstantin    schedule 25.02.2015

вы не передаете закрытый ключ на стороне клиента соединения, поэтому я не знаю, как он может установить ssl без него.

Кроме того, поскольку вы используете тот же магазин ca, уверены ли вы, что не перезаписали сертификат при импорте.

person Zielu    schedule 26.02.2015
comment
(удален комментарий; извините, я пропустил 2-way) - person dave_thompson_085; 26.02.2015
comment
Спасибо всем. Я нашел классы JdkSslClientContext и JdkSslServerContext, которые мне помогают. - person Nik; 28.02.2015

Я обновил свой первый пост частями Обновление 1 и Обновление 2.

person Nik    schedule 02.03.2015