Skip to main content

Mutual TLS (mTLS)

Transport Layer Security (TLS) is the protocol that makes https:// work. Mutual TLS is its two-way variant where both the client and the server prove their identity. Every connection in this system uses mTLS.


Regular TLS vs Mutual TLS

Regular TLS (one-way)Mutual TLS (mTLS)
Server has certificate✅ Yes✅ Yes
Client verifies server✅ Yes✅ Yes
Client has certificate❌ No✅ Yes
Server verifies client❌ No✅ Yes
ExampleEvery https:// websiteCorporate VPNs, zero-trust networks

In one-way TLS, the server says "here's my ID" and the client checks it. The client remains anonymous.

In mTLS, both sides say "here's my ID" and both sides check. This means:

  • Only authorized clients can connect at all
  • Man-in-the-middle attacks are impossible (the attacker can't forge a valid certificate)
  • Every connection is mutually authenticated

The Handshake

Steps 3 and 6 are the critical security checks:

  • If the server's certificate isn't signed by the CA in the client's truststore → connection rejected
  • If the client's certificate isn't signed by the CA in the server's truststore → connection rejected

Certificates and Keystores

What's a Certificate?

A digital certificate is like a passport. It contains:

  • Subject: The identity being certified (e.g., "CN=ReplicaNode0")
  • Public key: The certificate holder's public encryption key
  • Issuer: Who vouches for this identity (the Certificate Authority)
  • Digital signature: The CA's cryptographic signature proving the certificate is authentic

What's a Keystore?

A .jks (Java KeyStore) file holds your own private key and certificate. You keep this secret.

What's a Truststore?

A truststore holds the certificates of entities you trust. In our project, every component's truststore contains exactly one certificate: the CA's public certificate.

This means: "I trust anyone whose certificate was signed by this CA."

Our Certificate Hierarchy

All certificates are signed by a single, self-signed Certificate Authority. Every component has the CA's public certificate in its truststore, so every component trusts every other component.

How We Configure mTLS in Java RMI

Server Side

// 1. Load keystore (our own identity)
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("certs/node0.jks"), "changeit".toCharArray());

// 2. Load truststore (who we trust)
KeyStore ts = KeyStore.getInstance("JKS");
ts.load(new FileInputStream("certs/truststore.jks"), "changeit".toCharArray());

// 3. Build SSLContext with both keystore and truststore
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "changeit".toCharArray());

TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ts);

SSLContext sslCtx = SSLContext.getInstance("TLS");
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());

// 4. Create socket factories
SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory(null, null, true);
// ^^^^
// require client authentication

Client Side

// Same keystore/truststore loading, but exports differently:
// The client presents client.jks when connecting
System.setProperty("javax.net.ssl.keyStore", "certs/client.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "changeit");
System.setProperty("javax.net.ssl.trustStore", "certs/truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");

// Connect using SSL socket factory
Registry registry = LocateRegistry.getRegistry("localhost", 1098, new SslRMIClientSocketFactory());

What mTLS Protects Against

AttackProtection
Eavesdropping (passive sniffing)All traffic is encrypted — attacker sees only ciphertext
Man-in-the-Middle (active interception)Both sides present certificates → attacker can't impersonate either side
Unauthorized clientsServer requires client certificate → only clients with valid certs can connect
Connection hijackingTLS session keys are ephemeral — can't be reused

The generate-certs.sh Script

# Creates a CA, then generates and signs certificates for all 5 entities
./scripts/generate-certs.sh

Output:

certs/
├── ca.jks # CA private key + self-signed cert
├── truststore.jks # Everyone trusts this (contains CA cert)
├── auth.jks # AuthService identity
├── node0.jks # Node 0 identity
├── node1.jks # Node 1 identity
├── node2.jks # Node 2 identity
└── client.jks # Client identity

All passwords: changeit (demo only — never use in production)


Next: Implementation Deep-Dive

Now you understand all the foundational concepts. Let's look at how they're implemented in code. → Package Structure Map