Keystores and truststores

Keystores and truststores are foundational to securing communication in production deployments. This guide explains what each is, how to create them, and how to configure them in Ballerina services and clients.

ConceptWhat it storesPrimary use
KeystorePrivate key + the service's own certificateProves the identity of your service to clients
TruststoreTrusted CA or peer certificates (public certs only)Decides which remote parties your service trusts

Prerequisites

The examples below use the Java keytool utility, which is bundled with every JDK. Verify it is available:

Copy
$ keytool -version

For production deployments you will also need access to a Certificate Authority (CA) or a PKCS12 certificate bundle (.p12) already issued by your CA.

Create a keystore

A keystore stores your service's private key and its certificate. Use the following steps to create a new keystore.

Step 1 — Generate a new key pair and self-signed certificate

Use a self-signed certificate during development and testing only. Always replace it with a CA-signed certificate in production.

Copy
$ keytool -genkeypair \
  -alias integration \
  -keyalg RSA \
  -keysize 2048 \
  -sigalg SHA256withRSA \
  -validity 365 \
  -keystore keystore.p12 \
  -storetype PKCS12 \
  -storepass <keystore-password> \
  -keypass <keystore-password> \
  -dname "CN=integration.example.com, OU=Engineering, O=Example Inc, L=Colombo, ST=Western, C=LK"
FlagDescription
-aliasLogical name for this key entry inside the keystore
-keyalg RSA -keysize 2048RSA 2048-bit key (use 4096 for higher security requirements)
-sigalg SHA256withRSASignature algorithm; SHA256withRSA is the minimum recommended
-validity 365Certificate validity in days
-storetype PKCS12The keystore type
-storepassPassword to protect the keystore file
-dnameDistinguished name embedded in the certificate

Step 2 — Generate a Certificate Signing Request (CSR)

This step is required for production environments. Skip it if you are using a self-signed certificate.

Copy
$ keytool -certreq \
  -alias integration \
  -keystore keystore.p12 \
  -storetype PKCS12 \
  -storepass <keystore-password> \
  -file integration.csr

Submit integration.csr to your CA. The CA returns a signed certificate file (e.g., integration.crt) together with any intermediate CA certificates.

Step 3 — Import the CA-signed certificate into the keystore

Skip this step if you are using a self-signed certificate. Otherwise, import the CA certificate chain (root and any intermediates) so that keytool can verify the chain of trust.

Copy
# Import the root CA certificate
$ keytool -importcert \
  -alias rootCA \
  -file rootCA.crt \
  -keystore keystore.p12 \
  -storetype PKCS12 \
  -storepass <keystore-password> \
  -noprompt

# Import an intermediate CA certificate (if present)
$ keytool -importcert \
  -alias intermediateCA \
  -file intermediateCA.crt \
  -keystore keystore.p12 \
  -storetype PKCS12 \
  -storepass <keystore-password> \
  -noprompt

Then import the signed service certificate under the same alias used when generating the key pair:

Copy
$ keytool -importcert \
  -alias integration \
  -file integration.crt \
  -keystore keystore.p12 \
  -storetype PKCS12 \
  -storepass <keystore-password>

Verify the keystore

Copy
$ keytool -list -keystore keystore.p12 -storetype PKCS12 -storepass <keystore-password> -v

The output lists all entries. Confirm the integration entry shows type PrivateKeyEntry and that the certificate chain is complete.

Create a truststore

A truststore holds the public certificates of CAs (or specific peers) that your service should trust. How you populate it depends on whether you are using CA-signed or self-signed certificates.

CA-signed certificates

Import the root CA certificate (and any intermediates) from your CA into the truststore. You do not need to import individual service certificates — any certificate signed by a trusted CA is automatically trusted.

Copy
$ keytool -importcert \
  -alias rootCA \
  -file rootCA.crt \
  -keystore truststore.p12 \
  -storetype PKCS12 \
  -storepass <truststore-password> \
  -noprompt

Repeat the command for each intermediate CA certificate if your chain includes them. Use a distinct -alias for each entry.

Self-signed certificates

With self-signed certificates, there is no CA, so you must import each peer's certificate individually.

First, export the certificate from the peer's keystore:

Copy
$ keytool -exportcert \
  -alias integration \
  -keystore keystore.p12 \
  -storetype PKCS12 \
  -storepass <keystore-password> \
  -file integration.crt \
  -rfc

Then import the exported certificate into your truststore:

Copy
$ keytool -importcert \
  -alias integration \
  -file integration.crt \
  -keystore truststore.p12 \
  -storetype PKCS12 \
  -storepass <truststore-password> \
  -noprompt

Repeat this for every peer that uses a self-signed certificate, using a unique -alias each time.

Verify the truststore

Copy
$ keytool -list -keystore truststore.p12 -storetype PKCS12 -storepass <truststore-password>

Configure Ballerina services

The examples below show how to use the keystore and truststore files in Ballerina services.

HTTP client with TLS

One-way TLS authenticates the server to the client. The client only needs a truststore containing the CA that signed the server's certificate.

Copy
import ballerina/http;

configurable string truststorePath = ?;
configurable string truststorePassword = ?;

http:Client secureClient = check new ("https://api.example.com", {
    secureSocket: {
        cert: {
            path: truststorePath,
            password: truststorePassword
        }
    }
});

Mutual TLS (mTLS) for HTTP

Mutual TLS requires both sides to authenticate. The server presents its certificate (from its keystore) and validates the client certificate against its truststore, and the client does the same in reverse.

HTTP listener — server side

Copy
import ballerina/http;

configurable string keystorePath = ?;
configurable string keystorePassword = ?;
configurable string truststorePath = ?;
configurable string truststorePassword = ?;

listener http:Listener secureListener = new (9443, {
    secureSocket: {
        key: {
            path: keystorePath,
            password: keystorePassword
        },
        mutualSsl: {
            verifyClient: http:REQUIRE,
            cert: {
                path: truststorePath,
                password: truststorePassword
            }
        }
    }
});

HTTP client — client side

Copy
import ballerina/http;

configurable string keystorePath = ?;
configurable string keystorePassword = ?;
configurable string truststorePath = ?;
configurable string truststorePassword = ?;

http:Client mtlsClient = check new ("https://api.example.com", {
    secureSocket: {
        key: {
            path: keystorePath,
            password: keystorePassword
        },
        cert: {
            path: truststorePath,
            password: truststorePassword
        }
    }
});

gRPC with mutual TLS

gRPC uses the same secureSocket structure. The example below shows a secured gRPC listener with mutual TLS enabled.

Copy
import ballerina/grpc;

configurable string keystorePath = ?;
configurable string keystorePassword = ?;
configurable string truststorePath = ?;
configurable string truststorePassword = ?;

listener grpc:Listener secureGrpcListener = new (9090, {
    secureSocket: {
        key: {
            path: keystorePath,
            password: keystorePassword
        },
        mutualSsl: {
            verifyClient: grpc:REQUIRE,
            cert: {
                path: truststorePath,
                password: truststorePassword
            }
        }
    }
});

Externalizing passwords

All keystore and truststore paths and passwords are declared as configurable variables in the examples above. Never hardcode them in source code.

Environment variables:

Ballerina maps BAL_CONFIG_VAR_<name> environment variables to configurable variables at runtime. Use this for secrets so they are never written to disk:

Copy
$ export BAL_CONFIG_VAR_keystorePassword="<keystore-password>"
$ export BAL_CONFIG_VAR_truststorePassword="<truststore-password>"

Config.toml:

Copy
keystorePath = "/opt/ballerina/security/keystore.p12"
truststorePath = "/opt/ballerina/security/truststore.p12"

References