Netty SslHandler не работает для собственного корневого сертификата

Я использую Netty 4.1.25. Я создаю X509Certificate из файла pem в моем коде. Используя этот сертификат, я настраиваю SslHandler следующим образом:

SslContext context = SslContextBuilder.forClient().trustManager( certificate ).build();
pipeline.addLast( "ssl", context.newHandler( socketChannel.alloc() ) );

Когда сертификат является точным сертификатом, который находится на сервере (сервер является устройством IoT, но, вероятно, не имеет значения), это работает нормально.

Однако когда я создаю собственный корневой сертификат и:

  • на сервере есть сертификат, доверяющий корневому сертификату
  • приложение Netty использует корневой сертификат

Тогда я получаю это исключение:

io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:459) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:647) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysPlain(NioEventLoop.java:547) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:501) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:461) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:128) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:264) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:259) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:642) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:461) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:361) ~[na:na]
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392) ~[na:na]
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444) ~[na:na]
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1065) ~[na:na]
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1052) ~[na:na]
    at java.base/java.security.AccessController.doPrivileged(Native Method) ~[na:na]
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:999) ~[na:na]
    at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1435) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1343) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1177) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1221) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428) ~[netty-all-4.1.25.Final.jar:4.1.25.Final]
    ... 16 common frames omitted
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385) ~[na:na]
    at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:290) ~[na:na]
    at java.base/sun.security.validator.Validator.validate(Validator.java:264) ~[na:na]
    at java.base/sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:321) ~[na:na]
    at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:279) ~[na:na]
    at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:141) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:620) ~[na:na]
    ... 30 common frames omitted
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141) ~[na:na]
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126) ~[na:na]
    at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297) ~[na:na]
    at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380) ~[na:na]
    ... 36 common frames omitted

Для тестирования я также использовал опцию SslContextBuilder, чтобы напрямую использовать TrustManagerFactory вместо сертификата:

TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
factory.init(certificateService.getKeyStore());
SslContext context = SslContextBuilder.forClient().trustManager(factory).build();

Однако это дает ту же проблему.

Интересно, что это IoT-устройство также имеет веб-интерфейс, использующий тот же сертификат. Когда я открываю этот URL, используя тот же TrustManagerFactory, это работает без проблем:

SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, factory.getTrustManagers(), new SecureRandom());

HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
URL url = new URL("https://10.65.101.155");
HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();

Используя urlConnection, я могу распечатать содержимое веб-страницы и просмотреть информацию о сертификате.

Я уже работал с -Djava.security.debug=all, но, похоже, это много информации, которую я не совсем понимаю, боюсь.

Почему через URL работает, а через Netty нет, используя тот же TrustManagerFactory? Могу ли я что-нибудь сделать на стороне Netty для дальнейшей отладки? Есть ли возможность распечатать сертификат, который Netty получает от сервера?

Вы пробовали использовать SslContext.newHandler(ByteBufAllocator alloc, String peerHost, int peerPort)?

Norman Maurer 28.12.2018 21:01

@NormanMaurer Да, к сожалению, такой же результат.

Wim Deblauwe 03.01.2019 08:22

@NormanMaurer См. Ответ сейчас, в конце концов, это не проблема Netty.

Wim Deblauwe 03.01.2019 12:30
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
3
691
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Наконец разобрался в чем дело. Устройство IoT использует nginx для сертификата веб-интерфейса, но парализатор для сертификата, который Netty использует для связи.

Мне удалось это увидеть с помощью openssl CLI:

openssl s_client -showcerts -connect 10.65.101.155:6000

Ответ этой команды не показал правильную цепочку сертификатов, в то время как выполнение той же команды с портом 443 вместо 6000 действительно показало хорошую цепочку доверия.

Кажется, что stunnel требует, чтобы файл .pem содержал конкатенацию файлов .pem сертификата «корневого» и «конечного объекта» в одном файле (спасибо https://serverfault.com/a/254804/209813).

После такой смены устройства IoT связь Netty теперь также в порядке.

Другие вопросы по теме