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

Unknown Certificate Issue with Intermediate and Root CA Certificates #3532

Open
MohammadNC opened this issue Dec 5, 2024 · 2 comments
Open
Assignees
Labels
for/user-attention This issue needs user attention (feedback, rework, etc...) status/need-feedback type/bug A general bug

Comments

@MohammadNC
Copy link

While working Intermediate and Root CA Certificates getting Unknown Certificate

+--- org.springframework.boot:spring-boot-starter-webflux:2.7.22.3
| +--- org.springframework.boot:spring-boot-starter:2.7.22.3 ()
| +--- org.springframework.boot:spring-boot-starter-json:2.7.22.3 (
)
| +--- org.springframework.boot:spring-boot-starter-reactor-netty:2.7.22.3
| | --- io.projectreactor.netty:reactor-netty-http:1.0.48
| | +--- io.netty:netty-codec-http:4.1.112.Final
| | | +--- io.netty:netty-common:4.1.112.Final
| | | +--- io.netty:netty-buffer:4.1.112.Final
| | | | --- io.netty:netty-common:4.1.112.Final
| | | +--- io.netty:netty-transport:4.1.112.Final
| | | | +--- io.netty:netty-common:4.1.112.Final
| | | | +--- io.netty:netty-buffer:4.1.112.Final (*)
| | | | --- io.netty:netty-resolver:4.1.112.Final
| | | | --- io.netty:netty-common:4.1.112.Final
| | | +--- io.netty:netty-codec:4.1.112.Final
| | | | +--- io.netty:netty-common:4.1.112.Final

Expected Behavior

TLS Handshake should be success and connection should get establish

Actual Behavior

TLS Handshake is failing with unknow Certificate

Steps to Reproduce

  1. create a root CA
  2. create two different intermediate certificates one for Client and other for server
  3. Sing the client certificates with client intermediate ca and server certificates with server intermediate ca
  4. send the request from client to server
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.jetty.http.HttpHeader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.http.client.reactive.ReactorResourceFactory;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import reactor.netty.resources.LoopResources;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.util.List;


@Component
public class NettyServerFactorySslCustomizer {

    private static Logger logger = LogManager.getLogger(NettyServerFactorySslCustomizer.class);

    @Value("${server.port}")
    private Integer httpsPort;

    @Value("${server.tcp.keepAlive.interval}")
    private int keepAliveInterval;

    @Value("${server.tcp.keepAlive.idealTime}")
    private int keepAliveIdleTime;

    @Value("${server.tcp.keepAlive.probe}")
    private int keepAliveProbe;

    @Value("${server.tcp.keepAlive.enabled}")
    private boolean keepAliveEnabled;

    @Value("${ssl.needClientAuth:false}")
    private boolean clientAuth;

    @Autowired
    HttpHandler httpHandler;

    WebServer http;

    @Bean
    public HttpHandler handler(ApplicationContext context) {
        return WebHttpHandlerBuilder.applicationContext(context).build();
    }

    @PreDestroy
    public void stop() {
        this.http.stop();
    }

    @PostConstruct
    public void start() throws Exception {
        try {
            logger.warn("Enabling https port " + httpsPort + " on netty server");
            LoopResources resource = LoopResources.create("ingress-h2");
            ReactorResourceFactory reactorResourceFactory = new ReactorResourceFactory();
            reactorResourceFactory.setLoopResources(resource);
            reactorResourceFactory.setUseGlobalResources(false);
            NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(httpsPort);
            factory.setResourceFactory(reactorResourceFactory);          
           

            if(keepAliveEnabled && Epoll.isAvailable()) {
                factory.addServerCustomizers(builder -> builder.tcpConfiguration(tcpServer ->
                        tcpServer
                                .option(ChannelOption.SO_KEEPALIVE, true)
                                .option(EpollChannelOption.TCP_KEEPIDLE,keepAliveIdleTime)
                                .option(EpollChannelOption.TCP_KEEPCNT,keepAliveProbe)
                                .option(EpollChannelOption.TCP_KEEPINTVL,keepAliveInterval)));
            }

            List<String> severCiphers = getSeverCiphers();
            Http2SecurityUtil.CIPHERS.clear();
            Http2SecurityUtil.CIPHERS.addAll(severCiphers);

            Http2 http2 = new Http2();
            http2.setEnabled(true);

            factory.setPort(httpsPort);
            factory.setHttp2(http2);
            factory.addServerCustomizers(server -> server.
                    tcpConfiguration(tcpServer-> tcpServer.doOnConnection(connection -> {
                        ChannelFuture closeFuture = connection.channel().closeFuture();
                        closeFuture.addListener(new ChannelFutureListener() {
                            @Override
                            public void operationComplete(ChannelFuture future) throws Exception {
                                if(!future.isSuccess()) {
                                    String host = ((InetSocketAddress)future.channel().remoteAddress()).getHostString();
                                    String port = String.valueOf(((InetSocketAddress)future.channel().remoteAddress()).getPort());
                                    String sourceAddress = host+":"+port;
                                    String destination = ((InetSocketAddress)future.channel().localAddress()).getHostString();
                                    String destinationPort = String.valueOf(((InetSocketAddress)future.channel().localAddress()).getPort());
                                    String destinationAddress = destination+":"+destinationPort;
                                }
                            }
                        });
                    })));
            factory.addServerCustomizers(httpServer -> httpServer.wiretap(true));
            factory.addServerCustomizers(httpServer ->httpServer.doOnConnection(conn -> conn.addHandlerLast(NettyCustomChannelHandler.INSTANCE)));

            logger.warn("before going to add ssl context server customizer..");
            factory.addServerCustomizers(httpServer -> httpServer.secure(sslContextSpec -> sslContextSpec.sslContext(buildSslContext())));


            this.http = factory.getWebServer(this.httpHandler);

            this.http.start();

        } catch (Exception e) {
			logger.error("start(): Netty Server Initialization Exception :{}", e.getMessage());
            throw e;
        }
    }

    public WebServer getHttpServer() {
        return http;
    }

    private SslContext buildSslContext() {
        try {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(new FileInputStream("/configinfo/PrimaryKeyStoreServer.jks"), "1234".toCharArray());

            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, "1234".toCharArray());

            // Load TrustStore (CA Certificates)
            KeyStore trustStore = KeyStore.getInstance("JKS");
            trustStore.load(new FileInputStream("/configinfo/TrustStoreServer.jks"), "1234".toCharArray());

            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trustStore);

            // Create and initialize the SSLContext
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);

            return SslContextBuilder.forServer(keyManagerFactory)
                    .trustManager(trustManagerFactory)
                    .protocols("TLSv1.2")
                    .clientAuth(ClientAuth.REQUIRE)
                    .ciphers(getSeverCiphers())
                    .build();
        } catch (Exception e) {
            throw new RuntimeException("Failed to create SSL context", e);
        }
    }

    static final class NettyCustomChannelHandler extends ChannelOutboundHandlerAdapter {

		static final NettyCustomChannelHandler INSTANCE = new NettyCustomChannelHandler();

		@Override
		public boolean isSharable() {
			return true;
		}

		@Override
		public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
			if (msg instanceof FullHttpResponse originalMsg) {
				/*As per RFC 7230 a server MUST NOT send a Content-Length header field in any response
				with a status code of 1xx (Informational) or 204 (No Content). A
				server MUST NOT send a Content-Length header field in any 2xx
				(Successful) response to a CONNECT request*/
				if ((originalMsg.status() == HttpResponseStatus.NO_CONTENT || CodeUtility.is1xx(originalMsg.status().code()))
						&& originalMsg.headers().contains(HttpHeader.CONTENT_LENGTH.toString()) ) {
					logger.debug("Removing content-length header from 1xx/204 response");
					originalMsg.headers().remove(HttpHeader.CONTENT_LENGTH.toString());
				}
				ctx.write(originalMsg, promise);
			}
			else {
				ctx.write(msg, promise);
			}
		}
	}

}

My TrustStoreServer.jks & PrimaryKeyStoreServer.jks contains both the intermediate and root ca

@MohammadNC MohammadNC added status/need-triage A new issue that still need to be evaluated as a whole type/bug A general bug labels Dec 5, 2024
@violetagg
Copy link
Member

@MohammadNC Thanks for getting in touch, but your Reactor Netty version is not supported anymore. Please update to a supported version (more info here)

@violetagg violetagg added for/user-attention This issue needs user attention (feedback, rework, etc...) and removed status/need-triage A new issue that still need to be evaluated as a whole labels Dec 9, 2024
@violetagg violetagg self-assigned this Dec 9, 2024
Copy link

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for/user-attention This issue needs user attention (feedback, rework, etc...) status/need-feedback type/bug A general bug
Projects
None yet
Development

No branches or pull requests

2 participants