Call internal APIs having private certificates

Learn how to make HTTPS calls from AEM to web APIs using private or self-signed certificates.

Transcript
In this tutorial, we will learn how to make HTTPS calls from Adobe Experience Manager to web APIs that use private or self-signed certificates. These certificates are commonly associated with internal or test APIs. Since they are not issued by recognized certificate authorities or CAs, they cannot inherently trusted by web browsers or the Java virtual machine. When attempting to invoke APIs secured with such self-signed certificates over HTTPS, you may encounter the suncert pathbuilder exception in your log files. However, by using the Apache HTTP client library and leveraging AEM’s global trust store, you can effectively make successful calls to these APIs. Let’s dive into the process. For the purpose of this demonstration, I have created a simple NodeJS application that operates an HTTPS server using a self-signed certificate. The API is available on port 3000 and as you can see, the certificate is not trusted by the web browser. The API responds with a JSON object containing the current date and time. Review the README file of this NodeJS application for further details. Additionally, I have written an AEM servlet in my weekend projects core module. This servlet invokes the self-signed certificate API running on the port 3000 using the HTTPS protocol. When executing the servlet, I receive a 500 HTTP response code accompanied by an error message. The log file contains the specifics of the encountered exception. Now, to ensure a successful API call, follow these steps. Begin by uploading the self-signed certificate into AEM’s global trust store. This action helps us to establish trust between AEM and the APIs secured by the private certificate. Next, create a new servlet or modify the existing servlet’s code to utilize the SSLContextBuilder object from the Apache HTTP client library. This object will load AEM’s trust store. The trust store is obtained by using the out-of-the-box KeyStore service provided by AEM. After deploying the modified code, I run the new servlet. This time, I receive a 200 HTTP response code and the correct API response. These changes ensure that the JVM’s default trust store is bypassed in favor of explicitly using AEM’s trust store during the SSL handshake. It is important to note that this specialized SSLContext handling method should exclusively be applied to self-signed API calls. For APIs secured with valid certificate authority issued certificates, regular code should be employed. Thank you. And now you know the intricacies of calling self-signed APIs from AEM while maintaining optimal security practices.

By default when trying to make an HTTPS connection to a web API that uses a self-signed certificate, the connection fails with the error:

PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

This issue typically occurs when the API’s SSL certificate is not issued by a recognized certificate authority (CA) and Java™ application cannot validate SSL/TLS certificate.

Let’s learn how to successfully call APIs that have private or self-signed certificates by using Apache HttpClient and AEM’s global TrustStore.

Prototypical API invocation code using HttpClient

The following code makes an HTTPS connection to a web API:

...
String API_ENDPOINT = "https://example.com";

// Create HttpClientBuilder
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

// Create HttpClient
CloseableHttpClient httpClient = httpClientBuilder.build();

// Invoke API
CloseableHttpResponse closeableHttpResponse = httpClient.execute(new HttpGet(API_ENDPOINT));

// Code that reads response code and body from the 'closeableHttpResponse' object
...

The code uses the Apache HttpComponent’s HttpClient library classes and their methods.

HttpClient and load AEM TrustStore material

To call an API endpoint that has private or self-signed certificate, the HttpClient’s SSLContextBuilder must be loaded with AEM’s TrustStore, and used to facilitate the connection.

Follow the below steps:

  1. Login to AEM Author as an administrator.

  2. Navigate to AEM Author > Tools > Security > Trust Store, and open the Global Trust Store. If accessing first time, set a password for the Global Trust Store.

    Global Trust Store

  3. To import a private certificate, click Select Certificate File button and select desired certificate file with .cer extension. Import it by clicking Submit button.

  4. Update Java™ code like below. Note that to use @Reference to get AEM’s KeyStoreService the calling code must be an OSGi component/service, or a Sling Model (and @OsgiService is used there).

    code language-java
    ...
    
    // Get AEM's KeyStoreService reference
    @Reference
    private com.adobe.granite.keystore.KeyStoreService keyStoreService;
    
    ...
    
    // Get AEM TrustStore using KeyStoreService
    KeyStore aemTrustStore = getAEMTrustStore(keyStoreService, resourceResolver);
    
    if (aemTrustStore != null) {
    
        // Create SSL Context
        SSLContextBuilder sslbuilder = new SSLContextBuilder();
    
        // Load AEM TrustStore material into above SSL Context
        sslbuilder.loadTrustMaterial(aemTrustStore, null);
    
        // Create SSL Connection Socket using above SSL Context
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslbuilder.build(), NoopHostnameVerifier.INSTANCE);
    
        // Create HttpClientBuilder
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        httpClientBuilder.setSSLSocketFactory(sslsf);
    
        // Create HttpClient
        CloseableHttpClient httpClient = httpClientBuilder.build();
    
        // Invoke API
        closeableHttpResponse = httpClient.execute(new HttpGet(API_ENDPOINT));
    
        // Code that reads response code and body from the 'closeableHttpResponse' object
        ...
    }
    
    /**
     *
     * Returns the global AEM TrustStore
     *
     * @param keyStoreService OOTB OSGi service that makes AEM based KeyStore
     *                         operations easy.
     * @param resourceResolver
     * @return
     */
    private KeyStore getAEMTrustStore(KeyStoreService keyStoreService, ResourceResolver resourceResolver) {
    
        // get AEM TrustStore from the KeyStoreService and ResourceResolver
        KeyStore aemTrustStore = keyStoreService.getTrustStore(resourceResolver);
    
        return aemTrustStore;
    }
    
    ...
    
    • Inject the OOTB com.adobe.granite.keystore.KeyStoreService OSGi service into your OSGi component.
    • Get the global AEM TrustStore using KeyStoreService and ResourceResolver, the getAEMTrustStore(...) method does that.
    • Create an object of SSLContextBuilder, see Java™ API details.
    • Load the global AEM TrustStore into SSLContextBuilder using loadTrustMaterial(KeyStore truststore,TrustStrategy trustStrategy) method.
    • Pass null for TrustStrategy in above method, it ensures that only AEM trusted certificates succeed during API execution.
CAUTION
API calls with valid CA-issued certificates fail when executed using the mentioned approach. Only API calls with AEM trusted certificates are allowed to succeed when following this method.
Use the standard approach for executing API calls of valid CA-issued certificates, meaning that only APIs associated with private certificates should be executed using the previously mentioned method.

Avoid JVM Keystore changes

A conventional approach to effectively invoke internal APIs with private certificates involves modifying the JVM Keystore. It is achieved by importing the private certificates using the Java™ keytool command.

However, this method is not aligned with security best practices and AEM offers a superior option through the utilization of the Global Trust Store and KeyStoreService.

Solution Package

The sample Node.js project demoed in the video can be downloaded from here.

The AEM servlet code is available in the WKND Sites Project’s tutorial/web-api-invocation branch, see.

recommendation-more-help
c92bdb17-1e49-4e76-bcdd-89e4f85f45e6