Mutual Transport Layer Security (mTLS) authentication from AEM

Learn how to make HTTPS calls from AEM to web APIs that require Mutual Transport Layer Security (mTLS) authentication.

Transcript
Let’s learn how to make HTTPS calls from Adobe Xfinity Manager to APIs that require mutual transport layer security, or MTLS for short. MTLS authentication mandates both client and server to present digital certificates, extending the security of one-way TLS authentication commonly used for web communication. Let’s first understand the challenge. When calling MTLS-protected APIs via HTTPS, you may encounter an SSL handshake exception. However, using the Apache HTTP client library and AEM’s user-specific key store, you can successfully make these calls. Let’s set up the API demo environment. For demonstration purposes, I have set up a Node.js application with an HTTPS server and a self-signed certificate, which requires MTLS authentication for successful responses. As you can see, I’m passing two additional parameters, request-cert and reject-unauthorize, both set to true. We can test this MTLS API by running the curl command with cert and key options and passing the required certificate file and private key file. For demo purposes, I have also created the client certificates. Review the README file of this Node.js project for further details. Next, let’s configure AEM. First, we need to create an AEM certificate. However, to expedite AEM setup and configuration, I’m using the client certificates provided by the sample Node.js project as an AEM certificate. Please note, these are internal certification authorities signed cert and not issued by a trusted certification authority. Next, I’m creating a user. However, you can add it to an existing user as well. After that, create the key store for this user and set the access password. Finally, import the private key and certificate file and give an alias to this certificate. Don’t forget to review the imported certificate details. Additionally, as we are using internal certification authorities signed certificates, both the API server and AEM upload the internal CS certificate to AEM’s global trust store. Finally, let’s update the AEM servlet code. When a regular AEM servlet calls this MTLS API using the HTTPS protocol, you receive a 500 HTTP response code accompanied by SSL Handshake exception. An informing AEM certificate is required. Next, create a new servlet or modify the existing servlet’s code to utilize the SSLContextBuilder object from the Apache HTTP client library. Then, load AEM’s key store and its password into it. For protection quality, the password should come from OSGI config. The key store is obtained from the demo user created earlier using the out-of-the-box key store service provided by AEM. In our case, we have to load the AEM’s global trust store as demo MTLS API is not using trusted CS cert as mentioned earlier. After deploying the modified code, run the new servlet. You should receive a 200 HTTP response code and the correct API response this time. With these changes, AEM sends its certificate when calling the MTLS protected API. The server validates it just as the AEM validates the server’s certificate. In summary, this mutual authentication enhances security by ensuring that both parties are who they claim to be. To summarize, we learned how to call MTLS APIs from AEM by leveraging digital certificates, AEM’s user specific key store and the Apache HTTP client library. We overcame SSL handshake exception, achieving mutual authentication and enhanced security in our API interactions. Thank you. Let’s keep learning.

The mTLS or two-way TLS authentication enhances the security of the TLS protocol by requiring both the client and the server to authenticate each other. This authentication is done by using digital certificates. It is commonly used in scenarios where strong security and identity verification are critical.

By default when trying to make an HTTPS connection to a web API that requires mTLS authentication, the connection fails with the error:

javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_required

This issue occurs when client does not present a certificate to authenticate itself.

Let’s learn how to successfully call APIs that require mTLS authentication by using Apache HttpClient and AEM’s KeyStore and TrustStore.

HttpClient and load AEM KeyStore material

At a high level, the following steps are required to call a mTLS protected API from AEM.

AEM Certificate Generation

Request the AEM certificate by partnering with your organization’s security team. The security team provides or asks the certificate-related details such as key, certificate signing request (CSR), and using CSR the certificate is issued.

For demo purposes, generate the certificate-related details such as key, certificate signing request (CSR). In below example, a self-signed CA is used to issue the certificate.

  • First, generate the internal Certification Authority (CA) certificate.

    code language-shell
    # Create an internal Certification Authority (CA) certificate
    openssl req -new -x509 -days 9999 -keyout internal-ca-key.pem -out internal-ca-cert.pem
    
  • Generate the AEM certificate.

    code language-shell
    # Generate Key
    openssl genrsa -out client-key.pem
    
    # Generate CSR
    openssl req -new -key client-key.pem -out client-csr.pem
    
    # Generate certificate and sign with internal Certification Authority (CA)
    openssl x509 -req -days 9999 -in client-csr.pem -CA internal-ca-cert.pem -CAkey internal-ca-key.pem -CAcreateserial -out client-cert.pem
    
    # Verify certificate
    openssl verify -CAfile internal-ca-cert.pem client-cert.pem
    
  • Convert the AEM Private Key to DER format, AEM’s KeyStore requires the private key in DER format.

    code language-shell
    openssl pkcs8 -topk8 -inform PEM -outform DER -in client-key.pem -out client-key.der -nocrypt
    
TIP
The self-signed CA certificates are used for development purposes only. For production, use a trusted Certificate Authority (CA) to issue the certificate.

Certificate Exchange

If using a self-signed CA for the AEM certificate, like above, send the certificate or internal Certification Authority (CA) certificate to the API provider.

Also, if the API provider is using a self-signed CA certificate, receive the certificate or internal Certification Authority (CA) certificate from the API provider.

Certificate Import

To import AEM’s certificate, follow the below steps:

  1. Log in to AEM Author as an administrator.

  2. Navigate to AEM Author > Tools > Security > Users > Create or Select an existing user.

    Create or Select an existing user

    For demo purposes, a new user named mtl-demo-user is created.

  3. To open the User Properties, click the user name.

  4. Click Keystore tab and then click Create Keystore button. Then in the Set KeyStore Access Password dialog, set a password for this user’s keystore and click Save.

    Create Keystore

  5. In the new screen, under the ADD PRIVATE KEY FROM DER FILE section, follow the below steps:

    1. Enter Alias

    2. Import the AEM Private Key in DER format, generated above.

    3. Import the Certificate Chain Files, generated above.

    4. Click Submit

      Import AEM Private Key

  6. Verify that the certificate is imported successfully.

    AEM Private Key & Certificate Imported

If the API provider is using a self-signed CA certificate, import the received certificate into AEM’s TrustStore, follow the steps from here.

Likewise, if AEM is using self-signed CA certificate, request the API provider to import it.

Prototypical mTLS API invocation code using HttpClient

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

...

// Get AEM's KeyStoreService reference
@Reference
private com.adobe.granite.keystore.KeyStoreService keyStoreService;

...

// Get AEM KeyStore using KeyStoreService
KeyStore aemKeyStore = getAEMKeyStore(keyStoreService, resourceResolver);

if (aemKeyStore != null) {

    // Create SSL Context
    SSLContextBuilder sslbuilder = new SSLContextBuilder();

    // Load AEM KeyStore material into above SSL Context with keystore password
    // Ideally password should be encrypted and stored in OSGi config
    sslbuilder.loadKeyMaterial(aemKeyStore, "admin".toCharArray());

    // If API provider cert is self-signed, load AEM TrustStore material into above SSL Context
    // Get AEM TrustStore
    KeyStore aemTrustStore = getAEMTrustStore(keyStoreService, resourceResolver);
    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(MTLS_API_ENDPOINT));

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

/**
 * Returns the AEM KeyStore of a user. In this example we are using the
 * 'mtl-demo-user' user.
 *
 * @param keyStoreService
 * @param resourceResolver
 * @return AEM KeyStore
 */
private KeyStore getAEMKeyStore(KeyStoreService keyStoreService, ResourceResolver resourceResolver) {

    // get AEM KeyStore of 'mtl-demo-user' user, you can create a user or use an existing one.
    // Then create keystore and upload key, certificate files.
    KeyStore aemKeyStore = keyStoreService.getKeyStore(resourceResolver, "mtl-demo-user");

    return aemKeyStore;
}

/**
 *
 * 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 user’s AEM KeyStore using KeyStoreService and ResourceResolver, the getAEMKeyStore(...) method does that.
  • If the API provider is using a self-signed CA certificate, get the global AEM TrustStore, the getAEMTrustStore(...) method does that.
  • Create an object of SSLContextBuilder, see Java™ API details.
  • Load the user’s AEM KeyStore into SSLContextBuilder using loadKeyMaterial(final KeyStore keystore,final char[] keyPassword) method.
  • The keystore password is the password that was set when creating the keystore, it should be stored in OSGi config, see Secret Configuration Values.

Avoid JVM Keystore changes

A conventional approach to effectively invoke mTLS 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 User specific KeyStores and Global TrustStore 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