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 |
| Example | Every https:// website | Corporate 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
| Attack | Protection |
|---|---|
| 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 clients | Server requires client certificate → only clients with valid certs can connect |
| Connection hijacking | TLS 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