DevShivmohan / Learning-everything

Learning for developer only
0 stars 1 forks source link

[SSL] - SSL configuration in spring boot and RestTemplate config with CN verifier #47

Open DevShivmohan opened 4 months ago

DevShivmohan commented 4 months ago

Server side generate Open SSL and configure it in Spring application

Generate a private key

openssl genpkey -algorithm RSA -out server.key

Generate a self-signed certificate openssl req -new -x509 -key server.key -out server.crt -days 365

Create a PKCS12 keystore openssl pkcs12 -export -in server.crt -inkey server.key -out keystore.p12 -name tomcat

Configure Spring Boot to Use SSL

server.port=8443
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=changeit
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=tomcat

Client-Side Configuration

Trust the Server's Certificate Import the server's certificate into a trust store keytool -import -alias server-cert -file server.crt -keystore truststore.jks -storepass changeit

Spring boot application create bean of RestTemplate with SSL certificate

Use maven dependency below


   <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 -->
   <dependency>
       <groupId>org.apache.httpcomponents.client5</groupId>
       <artifactId>httpclient5</artifactId>
       <version>5.3.1</version>
   </dependency>

   <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5 -->
   <dependency>
       <groupId>org.apache.httpcomponents.core5</groupId>
       <artifactId>httpcore5</artifactId>
       <version>5.2.5</version>
   </dependency>

Configure RestTemplate on SSL certificate


package com.xcelore.common.beans;

import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;

@Configuration
public class RestTemplateConfig {

   @Bean
   public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
       KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
       try (FileInputStream trustStoreStream = new FileInputStream(new File("/home/admin1/xcelore-deck/aws-workstation-server/aws-workstation-server/aws-api/src/main/resources/cert/truststore.jks"))) {
           trustStore.load(trustStoreStream, "Test@123".toCharArray());
       }
       SSLContext sslContext = SSLContextBuilder.create()
               .loadTrustMaterial(trustStore, (chain, authType) -> true) // Trust all certificates
               .build();
       SSLConnectionSocketFactory sslConFactory = new SSLConnectionSocketFactory(sslContext);
       HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
               .setSSLSocketFactory(sslConFactory)
               .build();
       CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
       HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
       return builder
               .requestFactory(() -> factory)
               .build();
   }
}
DevShivmohan commented 4 months ago

Configure beans of client side

package com.xcelore.common.beans;

import com.xcelore.archives.mapper.ArchiveMapper; import com.xcelore.central.properties.CentralServerProperties; import com.xcelore.common.mapper.GlobalSettingMapper; import com.xcelore.common.properties.DataSourceProperties; import com.xcelore.landing.mapper.LandingMapper; import com.xcelore.metar.mapper.MetarMapper; import com.xcelore.metar.utils.MetarUtils; import com.xcelore.synop.mapper.SynopMapper; import com.xcelore.taf.mapper.TafMapper; import com.xcelore.user.mappers.UserDetailMapper; import com.xcelore.warnings.mapper.WarningMapper; import com.xcelore.weather.entity.Derived; import com.xcelore.weather.mapper.DerivedMapper; import com.xcelore.weather.mapper.WeatherMapper; import com.xcelore.weather.model.kafka.WeatherData; import com.xcelore.weather.util.StatisticalUtils; import io.netty.handler.ssl.SslContextBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.core5.ssl.SSLContextBuilder; import org.jasypt.encryption.StringEncryptor; import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; import org.mapstruct.factory.Mappers; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.ClassPathResource; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import reactor.netty.http.client.HttpClient;

import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import javax.sql.DataSource; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap;

@Configuration @EnableConfigurationProperties({DataSourceProperties.class, CentralServerProperties.class}) @Component public class Beans { private final DataSourceProperties dataSourceProperties; private final CentralServerProperties centralServerProperties;

@Value("${xcelore.airbase.software.crypto-secret}")
private String cryptoSecretKey;

public Beans(DataSourceProperties dataSourceProperties, CentralServerProperties centralServerProperties) {
    this.dataSourceProperties = dataSourceProperties;
    this.centralServerProperties = centralServerProperties;
}

/**
 * one siteId has one weather data
 *
 * @return
 */
@Bean
public ConcurrentMap<Integer, WeatherData> concurrentSingleWeatherDataMap() {
    return new ConcurrentHashMap<>();
}

/**
 * one siteId has last three weather data
 *
 * @return
 */
@Bean
public ConcurrentMap<Integer, List<WeatherData>> concurrentLastThreeWeatherDataMap() {
    return new ConcurrentHashMap<>();
}

/**
 * All beans related to scheduler data
 * @return
 */
@Bean
public SchedulerDataBean schedulerDataBean(){
    return new SchedulerDataBean();
}

@Bean
public MetarMapper metarMapper() {
    return Mappers.getMapper(MetarMapper.class);
}

@Bean
public SynopMapper synopMapper(){
    return Mappers.getMapper(SynopMapper.class);
}

@Bean
public LandingMapper landingMapper(){ return Mappers.getMapper(LandingMapper.class);}
@Bean
public WarningMapper warningMapper(){return Mappers.getMapper(WarningMapper.class);}
@Bean
public TafMapper tafMapper() {return Mappers.getMapper(TafMapper.class);}

@Bean
public MetarUtils metarUtils(){ return new MetarUtils();
}

@Bean
public WeatherMapper weatherMapper(){
    return Mappers.getMapper(WeatherMapper.class);
}

@Bean
public DerivedMapper derivedMapper() {
    return Mappers.getMapper(DerivedMapper.class);
}

@Bean
public StatisticalUtils statisticalUtils() {
    return new StatisticalUtils();
}

@Bean
public StringEncryptor stringEncryptor() {
    StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
    encryptor.setAlgorithm("PBEWithMD5AndDES");
    encryptor.setPassword("Xcelore.Airbase.SGS100@980$#&546312"); // same key used to encrypt
    return encryptor;
}

@Bean
@Primary
public DataSource dataSource() {
    return DataSourceBuilder.create()
            .url(dataSourceProperties.getUrl())
            .username(dataSourceProperties.getUsername())
            .password(new String(Base64.getDecoder().decode(dataSourceProperties.getPassword())))
            .build();
}

@Bean
public Cipher cipher() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException {
    byte[] key = new String(Base64.getDecoder().decode(cryptoSecretKey)).getBytes(StandardCharsets.UTF_8);
    final var sha = MessageDigest.getInstance("SHA-1");
    key = sha.digest(key);
    key = Arrays.copyOf(key, 16);
    final var secretKey = new SecretKeySpec(key, "AES");
    final var cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    return cipher;
}

@Bean
public ArchiveMapper archiveMapper() {
    return Mappers.getMapper(ArchiveMapper.class);
}

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    try (InputStream trustStoreStream = new ClassPathResource(centralServerProperties.getSslCertPath()).getInputStream()) {
        trustStore.load(trustStoreStream, centralServerProperties.getSslCertPassword().toCharArray());
    }
    SSLContext sslContext = SSLContextBuilder.create()
            .loadTrustMaterial(trustStore, (chain, authType) -> true) // Trust all certificates
            .build();
    SSLConnectionSocketFactory sslConFactory = new SSLConnectionSocketFactory(sslContext);
    HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
            .setSSLSocketFactory(sslConFactory)
            .build();
    CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
    return builder
            .requestFactory(() -> factory)
            .build();
}

/**
 *
 * Web Reactor client config with SSL cert
 * @return
 */
@Bean
public HttpClient httpClient() throws KeyStoreException, IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    try (InputStream trustStoreStream = new ClassPathResource(centralServerProperties.getSslCertPath()).getInputStream()) {
        trustStore.load(trustStoreStream, centralServerProperties.getSslCertPassword().toCharArray());
    } catch (CertificateException e) {
        throw new RuntimeException(e);
    }
    KeyManagerFactory keyManagerFactory =
            KeyManagerFactory.getInstance("SunX509");
    keyManagerFactory.init(trustStore, centralServerProperties.getSslCertPassword().toCharArray());
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(trustStore);
    final var sslContextBuilder = SslContextBuilder.forClient()
            .trustManager(trustManagerFactory)
            .keyManager(keyManagerFactory)
            .build();
    return HttpClient.create().secure(sslSpec -> sslSpec.sslContext(sslContextBuilder));
}

@Bean
public UserDetailMapper userMapper() {
    return Mappers.getMapper(UserDetailMapper.class);
}

@Bean
public GlobalSettingMapper globalSettingMapper() {
    return Mappers.getMapper(GlobalSettingMapper.class);
}

@Bean
public Derived derived() {
    return new Derived();
}

@Bean
public ApplicationDataBean applicationDataBean(){
    return new ApplicationDataBean();
}

}

DevShivmohan commented 4 months ago

Client side web reactor configuration with SSL cert configured web client

package com.xcelore.central.event;

import com.xcelore.central.dto.metar.MetarReportResponseDto; import com.xcelore.central.impl.AirbaseCentralCryptoBuilder; import com.xcelore.central.properties.CentralServerProperties; import com.xcelore.common.beans.ApplicationDataBean; import com.xcelore.common.constants.Constants; import com.xcelore.common.services.GlobalSettingService; import com.xcelore.weather.live.WebSocketMessageBroker; import jakarta.annotation.PostConstruct; import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import reactor.netty.http.client.HttpClient; import reactor.util.retry.Retry;

import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Objects;

import static com.xcelore.common.constants.Constants.SSE_DIVERSIONARY_METAR_REPORT;

@Component @Log4j2 @EnableConfigurationProperties(CentralServerProperties.class) @AllArgsConstructor public class SSECentralServerConsumer { private final CentralServerProperties centralServerProperties; private final AirbaseCentralCryptoBuilder airbaseCentralCryptoBuilder; private final ApplicationDataBean applicationDataBean; private final WebSocketMessageBroker webSocketMessageBroker; private final GlobalSettingService globalSettingService; private final HttpClient httpClient;

@PostConstruct
public void consumeEvents() {
    final var disposable = applicationDataBean
            .getDiversionaryMetarReportWebClientBuilder()
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .baseUrl(centralServerProperties.getBaseURL())
            .build()
            .get()
            .uri(SSE_DIVERSIONARY_METAR_REPORT)
            .accept(MediaType.TEXT_EVENT_STREAM)
            .header(HttpHeaders.AUTHORIZATION, Constants.BEARER_SPACE + airbaseCentralCryptoBuilder.getAirbaseDataModelAsEncryptedJsonString())
            .retrieve()
            .bodyToFlux(MetarReportResponseDto.class)
            .doOnSubscribe(subscription -> log.info("Subscribe event diversionary"))
            .retryWhen(Retry.backoff(15000, Duration.of(10, ChronoUnit.SECONDS))
                    .doBeforeRetry(retrySignal -> log.warn("Retrying connection attempt {}", retrySignal.totalRetries())))
            .subscribe(
                    this::broadCastIfNeeded,
                    error -> {
                        log.error("Error occurred {}", error.getMessage());
                        if (applicationDataBean.getDiversionaryMetarReportSSEDisposable() != null && !applicationDataBean.getDiversionaryMetarReportSSEDisposable().isDisposed()) {
                            applicationDataBean.getDiversionaryMetarReportSSEDisposable().dispose();
                        }
                        consumeEvents();
                    },
                    () -> log.info("Subscription connection for diversionary event completed")
            );
    applicationDataBean.setDiversionaryMetarReportSSEDisposable(disposable);
}

@Async
public void broadCastIfNeeded(final MetarReportResponseDto metarReportResponseDto) {
    log.info("Diversionary report received from station code {}", metarReportResponseDto.getStationCode());
    final var globalSetting = globalSettingService.fetchGlobalSetting();
    if (Objects.isNull(globalSetting)) {
        return;
    }
    if (Objects.equals(metarReportResponseDto.getStationCode(), globalSetting.getDiversionaryOneStationCode())) {
        webSocketMessageBroker.broadCastDiversionaryOneReport(metarReportResponseDto);
    }
    if (Objects.equals(metarReportResponseDto.getStationCode(), globalSetting.getDiversionaryTwoStationCode())) {
        webSocketMessageBroker.broadCastDiversionaryTwoReport(metarReportResponseDto);
    }
}

}

DevShivmohan commented 4 months ago

we need to push CN as IP if deploy on cloud

create san.cnf file and use the below config

[ req ]
default_bits       = 2048
prompt             = no
default_md         = sha256
distinguished_name = dn
req_extensions     = req_ext

[ dn ]
CN                 = 18.216.85.166

[ req_ext ]
subjectAltName     = @alt_names

[ alt_names ]
IP.1               = 18.216.85.166

Generate a Private Key:

openssl genpkey -algorithm RSA -out server.key -aes256

Generate the CSR using the Configuration File:

openssl req -new -key server.key -out server.csr -config san.cnf

Generate a Self-Signed Certificate with SAN:

openssl x509 -req -in server.csr -signkey server.key -out server.crt -days 365 -extensions req_ext -extfile san.cnf

DevShivmohan commented 2 months ago

Mutual TLS configuration

Understanding Certificates and Keys Before we dive into generating certificates, let’s clarify some terminology:

CA (Certificate Authority) - An entity that issues digital certificates. Certificate - A digital form of identification, like a passport, for your application. Private Key - A secret key that is used in conjunction with a public certificate to encrypt and decrypt data. CSR (Certificate Signing Request) - A request sent from an applicant to a CA to obtain a digital identity certificate. Truststore - A repository that holds trusted certificates (usually CA certificates). Keystore - A repository that holds certificates along with their private keys. Generating Certificates and Keys Here’s a simplified process to generate all the necessary files for mTLS:

Step 1: Generate the CA Certificate Create the CA’s Private Key: openssl genrsa -out ca.key 2048

  1. Self-Sign and Create the CA Certificate:

openssl req -x509 -new -nodes -key ca.key -sha256 -days 365 -out ca.pem This creates a self-signed CA certificate valid for 365 days.

Step 2: Generate Server and Client Certificates Generate Private Keys: For the server: openssl genrsa -out server.key 2048

For the client: openssl genrsa -out client.key 2048

  1. Generate CSRs:

For the server: openssl req -new -key server.key -out server.csr

For the client: openssl req -new -key client.key -out client.csr

  1. Sign CSRs with the CA Key:

For the server: openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.crt -days 365

For the client: openssl x509 -req -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.crt -days 365

Step 3: Create PKCS#12 Keystores Convert Certificates and Keys to PKCS#12 Format: For the server: openssl pkcs12 -export -out server.p12 -name "server" -inkey server.key -in server.crt -certfile ca.pem

For the client: openssl pkcs12 -export -out client.p12 -name "client" -inkey client.key -in client.crt -certfile ca.pem

Step 4: Create the Truststore Import the CA Certificate into a PKCS#12 Truststore: keytool -import -file ca.pem -alias "ca" -keystore truststore.p12 -storetype PKCS12 Why Trust a CA Instead of Individual Client Certificates in mTLS?

In mutual TLS configurations, servers typically trust a Certificate Authority (CA) rather than individual client certificates. This approach significantly enhances scalability, as the server can authenticate any client with a CA-signed certificate, rather than needing to keep an updated list of every client’s certificate. It simplifies management since the truststore doesn’t require updates with each client certificate renewal. Moreover, security remains robust if the CA’s issuance process is strict and its private key is secure, as the server can rely on the CA’s verification of clients. While there may be scenarios where trusting individual certificates is necessary, such as in a one-client setup, using a CA is the standard practice for systems handling multiple clients.

Configuring Spring Boot for mTLS With all the necessary files generated, you can configure your Spring Boot applications to use mTLS.

Server application.properties:

server.port=8443
server.ssl.key-store=classpath:cert/server.p12
server.ssl.key-store-password=[server_keystore_password]
server.ssl.key-store-type=PKCS12
server.ssl.client-auth=NEED
server.ssl.trust-store=classpath:cert/truststore.p12
server.ssl.trust-store-password=password
server.ssl.trust-store-type=PKCS12

Client application.properties:

client.ssl.key-store=classpath:cert/client.p12
client.ssl.key-store-password=[client_keystore_password]
client.ssl.trust-store=classpath:cert/truststore.p12
client.ssl.trust-store-password=password

Client-Side RestTemplate Configuration for mTLS On the client side, the RestTemplate bean needs to be configured to support mTLS with the necessary SSL context. Below is the configuration class that sets up the RestTemplate:


@Configuration
public class RestClientConfig {

    // Load keystore and truststore locations and passwords
    @Value("${client.ssl.trust-store}")
    private Resource trustStore;
    @Value("${client.ssl.key-store}")
    private Resource keyStore;
    @Value("${client.ssl.trust-store-password}")
    private String trustStorePassword;
    @Value("${client.ssl.key-store-password}")
    private String keyStorePassword;

    @Bean
    public RestTemplate restTemplate() throws Exception {
        // Set up SSL context with truststore and keystore
        SSLContext sslContext = new SSLContextBuilder()
                .loadKeyMaterial(
                        keyStore.getURL(),
                        keyStorePassword.toCharArray(),
                        keyStorePassword.toCharArray()
                )
                .loadTrustMaterial(
                        trustStore.getURL(),
                        trustStorePassword.toCharArray()
                )
                .build();

        // Configure the SSLConnectionSocketFactory to use NoopHostnameVerifier
        SSLConnectionSocketFactory sslConFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());

        // Use a connection manager with the SSL socket factory
        HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
                .setSSLSocketFactory(sslConFactory)
                .build();

        // Build the CloseableHttpClient and set the connection manager
        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(cm)
                .build();

        // Set the HttpClient as the request factory for the RestTemplate
        ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);

        return new RestTemplate(requestFactory);
    }
}

NoopHostnameVerifier is used for hostname verification which accepts any valid SSL session in this example for simplicity. For production, you would use a stricter hostname verifier.

Verification of Secure Communication Once you’ve set up your server and client with mutual TLS, it’s time to verify that the secure connection is in place and functioning correctly. Here’s how to do it:

Start the Server Launch the server application first. It will begin listening for incoming connections on the configured port (typically 8443 for HTTPS).

  1. Run the Client

With the server running, initiate the client application. The client is configured to reach out to the server’s /connect endpoint.

  1. Observe the Outcome

If the mTLS handshake is successful, the client will receive and display a message from the server’s controller — “Successfully connected!”. This confirms that the API call was successful and assures you that the trusted connection was established using mTLS.

The absence of any SSL handshake errors or SSLPeerUnverifiedException exceptions is a good indicator that the certificates and keystores are correctly set up and recognized on both ends. Congratulations, you've secured your applications with mutual TLS!

WebReactor Client config for MTLS


@Bean
    public HttpClient httpClient1() throws KeyStoreException, IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        try (InputStream trustStoreStream = centralServerProperties.getTrustStore().getInputStream()) {
            trustStore.load(trustStoreStream, centralServerProperties.getTrustStorePassword().toCharArray());
        } catch (CertificateException e) {
            throw new RuntimeException(e);
        }

        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        try (InputStream keyStoreStream = centralServerProperties.getKeyStore().getInputStream()) {
            keyStore.load(keyStoreStream, centralServerProperties.getKeyStorePassword().toCharArray());
        } catch (CertificateException e) {
            throw new RuntimeException(e);
        }

        // Initialize KeyManagerFactory with PKCS12 Keystore
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, centralServerProperties.getKeyStorePassword().toCharArray());

        // Initialize TrustManagerFactory with PKCS12 Truststore
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);

        // Create SslContext using the KeyManagerFactory and TrustManagerFactory
        SslContextBuilder sslContextBuilder = SslContextBuilder.forClient()
                .keyManager(keyManagerFactory)
                .trustManager(trustManagerFactory);
        return HttpClient.create().secure(sslSpec -> sslSpec.sslContext(sslContextBuilder));

    }