1. Introduction
The SSL/TLS handshake is the foundation of secure communication between clients and servers. Before any encrypted data can be exchanged, both sides must negotiate protocols, exchange certificates, and derive encryption keys. Understanding this handshake is essential for developers working with HTTPS, secure APIs, or mutual authentication systems.
2. Problem
Many developers use HTTPS without understanding how the SSL/TLS handshake works behind the scenes. This lack of understanding can lead to misconfigurations, insecure deployments, failed client authentication, and confusion when working with certificates, keystores, and truststores.
Without a clear explanation and practical examples, debugging SSL/TLS issues becomes difficult—especially in microservices and enterprise environments where certificate chains, intermediate CAs, and protocol versions vary.
3. Solution
The solution is to break down the SSL/TLS handshake process into clear steps—from the initial Client Hello to server certificate validation and session key derivation—and provide concrete examples for Java and Spring Boot. Additionally, include troubleshooting steps and tools to diagnose failures.
4. Implementation
Below is a detailed implementation: handshake steps, Java/Spring Boot examples, troubleshooting, mutual TLS, and testing/verification commands you can run locally.
4.1 TLS/SSL Handshake Steps
- Step 1: Client Hello — Client sends supported protocol versions, cipher suites, and a client random.
- Step 2: Server Hello — Server selects protocol and cipher suite; sends server random.
- Step 3: Server Certificate — Server sends X.509 certificate chain for identity proof.
- Step 4: (Optional) Client Certificate Request — In mTLS the server requests the client’s certificate.
- Step 5: Client Key Exchange — Client generates pre-master secret and encrypts it with the server’s public key (or uses ephemeral Diffie–Hellman).
- Step 6: Certificate Verification — Client verifies the server certificate using its truststore and performs hostname verification.
- Step 7: Session Key Derivation — Both sides derive symmetric keys from the pre-master secret + randoms.
- Step 8: Finished Messages — Both sides send finished messages (encrypted) confirming handshake integrity.
- Step 9: Encrypted Application Data — Secure data transfer begins using the derived symmetric keys.
4.2 Java / Spring Boot TLS Example (RestTemplate & HttpClient)
This section shows how to configure a Spring Boot application to call an HTTPS server and how the handshake is triggered automatically by the HTTP client.
4.2.1 Create / Import a Truststore
keytool -import -alias server-cert \
-file server.crt \
-keystore truststore.jks \
-storepass changeitThis command imports the server certificate into truststore.jks so the Java client trusts the server certificate chain during the handshake.
4.2.2 Spring Boot server SSL config (application.yml)
server:
port: 8443
ssl:
enabled: true
key-store: classpath:server.p12
key-store-password: changeit
key-store-type: PKCS12
key-alias: server4.2.3 RestTemplate using Apache HttpClient with SSLContext
@Configuration
public class SslConfig {
@Bean
public RestTemplate mtlsRestTemplate() throws Exception {
// Load truststore from classpath
KeyStore trustStore = KeyStore.getInstance("JKS");
try (InputStream in = new ClassPathResource("truststore.jks").getInputStream()) {
trustStore.load(in, "changeit".toCharArray());
}
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial(trustStore, null)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.build();
return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
}
}This RestTemplate will validate the server certificate against truststore.jks during the TLS handshake.
4.3 Troubleshooting & Diagnostics (Common Errors + Commands)
When handshakes fail you need targeted diagnostics. Use the following tools and checks first:
- Enable Java SSL debug to see the handshake transcript:
java -Djavax.net.debug=all -jar your-app.jarThis produces verbose output showing versions, cipher suites, certificate validation, and failure reasons.
- Use openssl s_client to inspect server certificates and negotiated protocol/cipher:
openssl s_client -connect secure.example.com:443 -showcertsKey things to check in the openssl output: certificate chain correctness, certificate expiry, supported TLS versions, and negotiated cipher.
- Check for common exceptions:
PKIX path building failed— missing intermediate CA or untrusted root.no cipher suites in common— client/server share no common ciphers or protocols.Received fatal alert: bad_certificate— server rejected client certificate (mTLS) or client presented wrong certificate.
To diagnose certificate chains use:
keytool -list -v -keystore truststore.jks -storepass changeitTo simulate handshake from the client side (Java) programmatically and inspect the session:
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
try (SSLSocket socket = (SSLSocket) factory.createSocket("secure.example.com", 443)) {
socket.startHandshake();
SSLSession session = socket.getSession();
System.out.println("Protocol: " + session.getProtocol());
System.out.println("Cipher: " + session.getCipherSuite());
}4.4 Mutual TLS (mTLS) Example — Client Keystore + Server Request
If the server requires client certificates (mTLS), the client must present a keystore containing its private key and certificate. Below is how to configure both server and client.
Server: request client cert in application.yml
server:
port: 8443
ssl:
enabled: true
key-store: classpath:server.p12
key-store-password: changeit
key-store-type: PKCS12
client-auth: need # require client certificateclient-auth: need forces the server to request and require a valid client certificate for the TLS handshake.
Client: configure keystore (containing private key + cert)
keytool -importkeystore \
-srckeystore client.p12 -srcstoretype PKCS12 -srcstorepass changeit \
-destkeystore client-keystore.jks -deststoretype JKS -deststorepass changeitThen load key material in the SSLContext so the client can present its certificate during handshake:
KeyStore keyStore = KeyStore.getInstance("JKS");
try (InputStream ksStream = new ClassPathResource("client-keystore.jks").getInputStream()) {
keyStore.load(ksStream, "changeit".toCharArray());
}
KeyStore trustStore = KeyStore.getInstance("JKS");
try (InputStream ts = new ClassPathResource("truststore.jks").getInputStream()) {
trustStore.load(ts, "changeit".toCharArray());
}
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(keyStore, "changeit".toCharArray()) // client key + cert
.loadTrustMaterial(trustStore, null) // trust server CA
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.build();
RestTemplate rest = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));With this configuration, the client will send its certificate when the server requests it during the handshake.
4.5 Testing & Verification
Use the following commands and approaches to verify the handshake and cert exchange end-to-end.
4.5.1 Use openssl s_client (server cert only)
openssl s_client -connect secure.example.com:443 -showcertsInspect the certificate chain, validity dates, and the negotiated protocol/cipher suite in the command output.
4.5.2 Test mTLS with openssl (client cert)
openssl s_client -connect secure.example.com:8443 \
-cert client.crt -key client.key -CAfile ca-chain.pemIf mTLS is configured correctly, the handshake completes and you will see the server response or an interactive TLS session.
4.5.3 Test using curl (client cert)
curl -v --cacert ca-chain.pem --cert client.pem --key client-key.pem https://secure.example.com:8443/secureUse verbose mode to view TLS negotiation details. Replace client.pem and client-key.pem with PEM files or use PKCS#12 via --cert client.p12:password.
4.5.4 Java unit / integration tests
// Example: JUnit test to verify handshake (uses HttpClient with SSLContext)
@Test
public void whenCallSecureEndpoint_thenNoException() {
// build SSLContext similar to application config
// create HttpClient with SSLContext and call endpoint
// assert HTTP 200 or expected response
}Running tests in a controlled environment (local server with known certs) makes it easy to validate certificate rotation and handshake behavior before production rollout.
5. Conclusion
Understanding the SSL/TLS handshake and how to configure keystores, truststores, and SSLContext is essential for secure client-server communication. You now have:
- A step-by-step TLS handshake breakdown
- Concrete Java & Spring Boot examples for one-way TLS and mTLS
- Troubleshooting commands and common error explanations
- Testing and verification commands (openssl, curl, Java debug)