apache-camelssl-certificatespring-camelmsal4j

Apache Camel+Email: msal4j.AcquireTokenByClientCredentialSupplier failed


I have an Apache Camel+Camel-email+Springboot project. The IMAP route in my camel-context file fails to start up with error org.apache.camel.FailedToStartRouteException: Failed to start route mail-route-imap because of null I added a few SSL specific parameters to my application.properties which I need for interaction with my back-end APIs. I wonder why since the IMAP connection with Exchange server is over an OAUTH2 Client Secret. I wonder why msal4j library is looking for an SSL handshake when it is supposed to use the myExchangeAuthenticator that I am passing in the IMAP url? Postscript: The IMAP route works fine if I remove the SSL parameters from my application-context.

ERROR | ForkJoinPool.commonPool-worker-1 | AuthenticationResultSupplier.java logException:155 | [Correlation ID: 272b48e9-bf32-4244-9b4b-61dd0cda568e] Execution of class com.microsoft.aad.msal4j.AcquireTokenByClientCredentialSupplier failed.
com.microsoft.aad.msal4j.MsalClientException: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at com.microsoft.aad.msal4j.HttpHelper.executeHttpRequest(HttpHelper.java:53)
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

 ERROR | main | SpringApplication.java reportFailure:818 | Application run failed
org.apache.camel.FailedToStartRouteException: Failed to start route mail-route-imap because of null
        at org.apache.camel.impl.engine.RouteService.setUp(RouteService.java:132)

camel-context.xml

<route id="mail-route-imap" autoStartup="true">     
<from id="office365" uri="imaps://outlook.office365.com:993?authenticator=#myExchangeAuthenticator&amp;debugMode=true&amp;mail.imaps.proxy.host={{proxyHost}}&amp;mail.imaps.proxy.port={{proxyPort}}&amp;mail.imaps.auth.mechanisms=XOAUTH2&amp;disconnect=true" />
<to uri="direct:someProcessChainAhead" />
</route>

application.properties

    # Enabling SSL Bench
    server.http2.enabled=true
    server.ssl.enabled=true
    server.ssl.enabl=TLSv1.2,TLSv1.3
    server.servlet.session.cookie.secure=true
    server.servlet.session.cookie.same-site=strict
    server.ssl.trust-store=/some-path-here/truststore.ks
    server.ssl.key-alias=nfmt
    server.ssl.key-store=/some-path-here/keystore.ks
    server.ssl.client-auth=want

**MainApplication.java**

`//initialized the SSL params in appContext

public static void main(String[] args) {
        MyVaultUtil.initialize();
        System.setProperty("javax.net.ssl.trustStore", "/some-path-here/truststore.ks");
        System.setProperty("javax.net.ssl.trustStorePassword", MyVaultUtil.getInstance()
                .getSecret(MyVaultUtil.SecretKeys.truststore_external_password.getKey()));
        System.setProperty("javax.net.ssl.trustStoreType", KeyStore.getDefaultType());
        System.setProperty("server.ssl.trust-store-password", MyVaultUtil.getInstance()
                .getSecret(MyVaultUtil.SecretKeys.truststore_external_password.getKey()));
        System.setProperty("server.ssl.key-store-password", MyVaultUtil.getInstance()
                .getSecret(MyVaultUtil.SecretKeys.keystore_external_password.getKey()));
        MainApplication.run(MainApplication.class, args);
    }

    @Bean //initializing inside MainApp
    MicrosoftExchangeOnlineOAuth2MailAuthenticator exchangeAuthenticator() {
        return new MicrosoftExchangeOnlineOAuth2MailAuthenticator(tenantId, clientId, clientSecret, userName);
    }`

Solution

  • My Apache Camel+Camel-email+Springboot project is a client project. Hence having these entries in the application.properties was not Ok nor setting them in the Main class. Instead I used a sslConfig (a dedicated class with the SSLContext intialization for my HTTP client) in my backend service and it worked to establish the certificate based call to the backend service.

    Steps:

    MyBackendService:

            @Autowired 
            SSLConfig sslConfig;
            
            public String getSomeServiceDetails(String myReqPayload){
                
                String finalResponse=null;
                HttpPost httpPost = new HttpPost("some-url-to-backend");
                // Set headers
                httpPost.setHeader("Content-Type", "application/json");
                // Set the request payload
                StringEntity entity = new StringEntity(myReqPayload, StandardCharsets.UTF_8);
                httpPost.setEntity(entity);
                try (CloseableHttpResponse response = sslConfig.getHttpClient().execute(httpPost)) {
                    // Check the response status and process the response
                    int statusCode = response.getStatusLine().getStatusCode();
                    if (statusCode == HttpStatus.SC_OK) {
                        //my business logic here
                        // Read and store the response entity once
                        finalResponse = EntityUtils.toString(response.getEntity());
                    }
                } catch (IOException e) {
                    //some exception handling here
                }
                return finalResponse;
            }
    

    MySSLConfig:

    @PostConstruct
        public void initSSLContext() {
            try {
                FileInputStream inputStream = new FileInputStream(
                        new File("/mydirectory/for/certificates/External/keystore.jks"));
                KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
                ks.load(inputStream, System.getenv("MY_KEYSTORE_STRING_IN_ENV").toCharArray());
                SSLContext sslContext = new SSLContextBuilder()
                        .loadTrustMaterial(new File("/mydirectory/for/certificates/External/truststore.jks"),
                                System.getenv("MY_TRUSTSTORE_STRING_IN_ENV").toCharArray())
                        .loadKeyMaterial(ks, System.getenv("MY_TRUSTSTORE_STRING_IN_ENV").toCharArray()).build();
                SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
    
                httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
    
            } catch (Exception e) {
                //some exception occurred
            }
        }