Call internal APIs having private certificates

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

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