Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NullPointerException using sniMapping with H2 + HTTP11 #3473

Closed
hamadodene opened this issue Oct 17, 2024 · 4 comments · Fixed by #3484
Closed

NullPointerException using sniMapping with H2 + HTTP11 #3473

hamadodene opened this issue Oct 17, 2024 · 4 comments · Fixed by #3484
Assignees
Labels
type/bug A general bug
Milestone

Comments

@hamadodene
Copy link

We are trying to configure the Reactor Netty HTTP server with H2 and HTTP11, also using sniMapping. However, we are encountering a NullPointerException (NPE).

We have reproduced the problem at https://github.com/NiccoMlt/demo-reverse-proxy/tree/nullpointer-micrometer.

Our use case requires us to provide a different certificate based on the domain. Therefore, we thought to use sniMapping.

However, by enabling both H2 + HTTP11 on the server side and making requests with an HTTP11 or H2 client, we encounter the following NPE:

java.lang.NullPointerException: Cannot invoke "io.netty.handler.ssl.SslHandler.handshakeFuture()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
at reactor.netty.channel.MicrometerChannelMetricsHandler$TlsMetricsHandler.channelActive(MicrometerChannelMetricsHandler.java:275)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:262)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:238)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:231)
at io.netty.handler.ssl.AbstractSniHandler.channelActive(AbstractSniHandler.java:157)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:260)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:238)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:231)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelActive(DefaultChannelPipeline.java:1395)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:258)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:238)
at io.netty.channel.DefaultChannelPipeline.fireChannelActive(DefaultChannelPipeline.java:894)
at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:521)
at io.netty.channel.AbstractChannel$AbstractUnsafe.access$200(AbstractChannel.java:428)
at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:485)
at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:469)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:1583)

If we instead configure the server only for H2 and make the request in H2, we get the following error:

ott 17, 2024 11:49:23 AM org.bouncycastle.jsse.provider.ProvTlsClient notifyConnectionClosed
INFORMAZIONI: [client #1 @3ecabaaf] disconnected from localhost:8443
Exception in thread "main" javax.net.ssl.SSLException: org.bouncycastle.tls.TlsFatalAlert: unexpected_message(10); Unsupported UNKNOWN(0)
at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:960)
at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:133)
at com.diennea.carapace.Main.main(Main.java:141)
Caused by: javax.net.ssl.SSLException: org.bouncycastle.tls.TlsFatalAlert: unexpected_message(10); Unsupported UNKNOWN(0)
at org.bouncycastle.jsse.provider.ProvSSLEngine.unwrap(Unknown Source)
at java.base/javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:679)
at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader.unwrapBuffer(SSLFlowDelegate.java:542)
at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader.processData(SSLFlowDelegate.java:438)
at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader$ReaderDownstreamPusher.run(SSLFlowDelegate.java:269)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$LockingRestartableTask.run(SequentialScheduler.java:182)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:207)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: org.bouncycastle.tls.TlsFatalAlert: unexpected_message(10); Unsupported UNKNOWN(0)
at org.bouncycastle.tls.RecordStream.checkRecordType(Unknown Source)
at org.bouncycastle.tls.RecordStream.previewRecordHeader(Unknown Source)
at org.bouncycastle.tls.TlsProtocol.safePreviewRecordHeader(Unknown Source)
at org.bouncycastle.tls.TlsProtocol.previewInputRecord(Unknown Source)
at org.bouncycastle.jsse.provider.ProvSSLEngine.getRecordPreview(Unknown Source)
... 11 more

In the repository, you will find the reproducible example.

@hamadodene hamadodene added status/need-triage A new issue that still need to be evaluated as a whole type/bug A general bug labels Oct 17, 2024
@violetagg violetagg self-assigned this Oct 17, 2024
@violetagg violetagg removed the status/need-triage A new issue that still need to be evaluated as a whole label Oct 17, 2024
@hamadodene
Copy link
Author

hamadodene commented Oct 17, 2024

It also reproduces by modifying the test here by adding the H2 protocol.

What I've noticed is that when the Channel is registered in AbstractChannelMetricsHandler, the SslHandler is null... so then in MicrometerChannelMetricsHandler/TlsMetricsHandler, HandshakeFuture is done with sslHandler, which is null...

I hope this can help you.

@violetagg violetagg added this to the 1.1.24 milestone Oct 19, 2024
violetagg added a commit that referenced this issue Oct 24, 2024
- The server preface will be sent after receiving SniCompletionEvent
- TLS handshake metrics will be registered after receiving SniCompletionEvent

Fixes #3473
@violetagg violetagg linked a pull request Oct 24, 2024 that will close this issue
@violetagg
Copy link
Member

violetagg commented Oct 24, 2024

@hamadodene #3484 should be fixing the issue. If you are able to give it a try, it will be great!

Thanks a lot for the description and the reproducible example!

@hamadodene
Copy link
Author

@violetagg
Thank you for the fix. We tried it, and now the NPE seems to be resolved.
I’d like to ask you for some additional information if you could help us. We’re trying to enable OCSP stapling using handlerConfigurator:

        final HttpServer httpServer = HttpServer
                .create()
                .host(HOST)
                .port(PORT)
                .protocol(HttpProtocol.H2, HttpProtocol.HTTP11)
                .secure(sslContextSpec -> sslContextSpec
                        .sslContext(sslContext)
                        .handlerConfigurator(getSslHandlerConsumer(issuer, httpsCertificate))
                        .addSniMapping("localhost", sslContextSpec1 -> sslContextSpec1
                                .sslContext(sslContextLocalhost)
                                .handlerConfigurator(getSslHandlerConsumer(issuer, httpsCertificateSni)))
                )


    private static Consumer<SslHandler> getSslHandlerConsumer(final X509Certificate issuer, final X509Certificate httpsCertificateSni) {
        return sslHandler -> {
            if (!(sslHandler.engine() instanceof ReferenceCountedOpenSslEngine engine)) {
                throw new RuntimeException("Unexpected SSL handler type: " + sslHandler.engine());
            }

            // Attempt to retrieve and set the OCSP response here
            try {
                final byte[] ocspResponse = getOcspResponse(issuer, httpsCertificateSni);
                if (ocspResponse != null) {
                    engine.setOcspResponse(ocspResponse);
                } else {
                    System.err.println("Failed to retrieve OCSP response. It is null.");
                }
            } catch (Exception e) {
                throw new RuntimeException("Failed to set OCSP response: " + e.getMessage(), e);
            }
        };
    }
private static byte[] getOcspResponse(final X509Certificate issuer, final X509Certificate certificate) throws OCSPException, GeneralSecurityException, IOException, OperatorCreationException, InterruptedException {
      final OCSPReq ocspRequest = generateOCSPRequest(issuer, certificate);
      final OCSPResp ocspResp = sendOCSPRequest(OCSP_RESPONDER_URL, ocspRequest.getEncoded());

      // Check the response status
      if (ocspResp.getStatus() != OCSPRespBuilder.SUCCESSFUL) {
          System.err.println("OCSP response is not successful: " + ocspResp.getStatus());
          return null;
      }

      final BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResp.getResponseObject();
      return basicResponse.getEncoded(); // Return the DER-encoded OCSP response
  }

But we're having trouble getting it to work. The server’s response always lacks the OCSP check. Do you have any ideas?

You can also test on the nullpointer-micrometer branch of our repo:
https://github.com/NiccoMlt/demo-reverse-proxy/tree/nullpointer-micrometer

We've already integrated your fix.

Thanks in advance

@hamadodene
Copy link
Author

In the end, we resolved the OCSP issue. The method we were using to verify the OCSP response was incorrect. In fact, it worked with OpenSSL but not with our code.

See https://github.com/NiccoMlt/demo-reverse-proxy/tree/nullpointer-micrometer-netty-client

Thanks a lot for your help

NiccoMlt added a commit to diennea/carapaceproxy that referenced this issue Dec 10, 2024
NiccoMlt added a commit to diennea/carapaceproxy that referenced this issue Dec 10, 2024
NiccoMlt added a commit to diennea/carapaceproxy that referenced this issue Dec 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type/bug A general bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants