# Debugging Java SSL/TLS Connections: A Guide to javax.net.debug

## Introduction

Understanding SSL/TLS handshakes is crucial for debugging secure connections in Java applications. In this guide, I'll walk you through a real-world example of capturing and analyzing an SSL/TLS handshake using Java's built-in debugging capabilities.

This article covers:
- How to enable SSL/TLS debugging in Java
- Real handshake flow with detailed timing
- Complete working example with `HttpURLConnection`
- Step-by-step breakdown of the TLS 1.2 handshake

---

## Enabling SSL/TLS Debug Output

To capture SSL/TLS debugging information, simply set this system property before establishing any connections:

```java
System.setProperty("javax.net.debug", "all");
```

This enables comprehensive logging of:
- Cipher suite negotiation
- Certificate validation
- Handshake messages
- Raw bytes sent/received
- Session information

## Real-World TLS 1.2 Handshake Flow

When you connect to an HTTPS endpoint (in our example: `https://httpbin.org/get`), the following handshake occurs:

### ASCII Diagram: Complete TLS 1.2 Handshake

```
CLIENT (Java App)                           SERVER (httpbin.org)
════════════════════════════════════════════════════════════════

        ┌─────────────────────────────────┐
        │  1. ClientHello                 │
        │  (22:20:06.819 CET)             │
        │  - Ciphers, Extensions          │
        │  - Random value                 │
        └────────────────────┬────────────┘
                             │
                             │ [Raw write]
                             │ TLS 1.2 record
                             ├──────────────────────────────────>
                                                                │
                                           ┌────────────────────┘
                                           │
                                           ▼
                            ┌──────────────────────────────────┐
                            │  2. ServerHello                  │
                            │  (22:20:06.924 CET)              │
                            │  - Selected cipher suite:        │
                            │    TLS_ECDHE_RSA_WITH_AES_128... │
                            │  - Server random                 │
                            │  - Session ID                    │
                            └────────────────┬──────────────────┘
                                             │
                                             │ [Raw read]
        ┌────────────────────────────────────┤
        │                                     │
        ▼                                     ▼
┌──────────────────────────────┐  ┌──────────────────────────────────┐
│  3. Server Certificate       │  │  4. ServerKeyExchange (ECDH)     │
│  (22:20:06.930 CET)          │  │  (22:20:06.950 CET)              │
│  - Certificate chain:        │  │  - ECDH parameters               │
│    └─ httpbin.org            │  │  - Server's ECDH public key      │
│    └─ Amazon RSA 2048 M      │  │  - Signature                     │
│    └─ Root CA                │  │                                  │
└──────────────────────────────┘  └──────────────────────────────────┘
        │                                    │
        └────────────────────┬───────────────┘
                             │
                             ▼
                    ┌──────────────────────┐
                    │  5. ServerHelloDone  │
                    │  (22:20:06.951 CET)  │
                    └────────┬─────────────┘
                             │
                             │ [Raw read]
        ┌────────────────────┘
        │
        ▼
┌───────────────────────────────────────────┐
│  6. ClientKeyExchange (ECDHE)             │
│  (22:20:06.951 CET)                       │
│  - Client's ECDH public key               │
│  - Compute shared secret (PreMasterKey)   │
└────────────────┬────────────────────────────┐
                 │                            │
                 │ [Raw write]                │
                 │ TLS 1.2 handshake         │
                 ├───────────────────────────────────────────────>
                 │                            │
                 ▼                            │
┌────────────────────────────────────────┐   │
│  7. ChangeCipherSpec (Client)          │   │
│  (22:20:06.952 CET)                    │   │
│  - Switch to encrypted traffic         │   │
└────────────────┬─────────────────────────┐ │
                 │                          │ │
                 │ [Raw write]              │ │
                 │ TLS 1.2 change spec      │ │
                 ├──────────────────────────────>
                 │                          │ │
                 ▼                          │ │
┌────────────────────────────────────────┐   │
│  8. Finished (Client)                  │   │
│  (22:20:06.954 CET)                    │   │
│  - MAC of all handshake messages       │   │
│  - Encrypted with session key          │   │
└────────────────┬─────────────────────────┐ │
                 │                          │ │
                 │ [Raw write]              │ │
                 │ TLS 1.2 handshake       │ │
                 ├──────────────────────────────>
                 │                          │ │
                 │                          │ │
                 │                   ┌──────┘ │
                 │                   │        │
                 │                   ▼        ▼
                 │        ┌──────────────────────────────┐
                 │        │  9. NewSessionTicket         │
                 │        │  (22:20:07.056 CET)          │
                 │        │  - For session resumption    │
                 │        └────────┬─────────────────────┘
                 │                 │
                 │                 │ [Raw read]
        ┌────────┴─────────────────┤
        │                          │
        ▼                          ▼
┌──────────────────────┐  ┌────────────────────────────┐
│  10. ChangeCipherSpec│  │  11. Server Finished       │
│  (Server)            │  │  (22:20:07.056 CET)       │
│  (22:20:07.056 CET)  │  │  - MAC of all handshake   │
└──────────────────────┘  │  - Encrypted with key     │
                          └────────┬──────────────────┘
                                   │
                                   │ [Raw read]
                    ┌──────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  ✓ HANDSHAKE COMPLETE        │
        │  (22:20:07.057 CET)          │
        │                              │
        │  Secure Channel Established  │
        │  - Ready for application     │
        │  - All data encrypted/MAC'd  │
        └───────────────────────────────┘
```

### Handshake Timing & Summary

| Step | Message Type | Direction | Timestamp | Duration |
|------|-------------|-----------|-----------|----------|
| 1 | ClientHello | Client → Server | 22:20:06.819 | - |
| 2 | ServerHello | Server → Client | 22:20:06.924 | +105ms |
| 3 | Certificate | Server → Client | 22:20:06.930 | +6ms |
| 4 | ServerKeyExchange | Server → Client | 22:20:06.950 | +20ms |
| 5 | ServerHelloDone | Server → Client | 22:20:06.951 | +1ms |
| 6 | ClientKeyExchange | Client → Server | 22:20:06.951 | - |
| 7 | ChangeCipherSpec | Client → Server | 22:20:06.952 | +1ms |
| 8 | Finished | Client → Server | 22:20:06.954 | +2ms |
| 9 | NewSessionTicket | Server → Client | 22:20:07.056 | +102ms |
| 10 | ChangeCipherSpec | Server → Client | 22:20:07.056 | - |
| 11 | Finished | Server → Client | 22:20:07.056 | - |
| **Total Duration** | | | **22:20:07.057** | **~238ms** |

### Negotiated Security Parameters

- **Protocol Version**: TLS 1.2
- **Cipher Suite**: `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` (0xC02F)
- **Key Exchange**: ECDHE (Elliptic Curve Diffie-Hellman Ephemeral)
- **Encryption**: AES-128-GCM
- **Hash Algorithm**: SHA256
- **Server Certificate**: httpbin.org (issued by Amazon RSA 2048 M)

### Negotiated Extensions

- ✓ `renegotiation_info` - Controls renegotiation
- ✓ `server_name` - SNI (Server Name Indication)
- ✓ `ec_point_formats` - Supported EC point formats (uncompressed)
- ✓ `extended_master_secret` - Enhanced security
- ✓ `session_ticket` - Session resumption capability

---

## Handshake Phase Breakdown

### Phase 1: Key Exchange & Authentication
The client and server exchange their capabilities and parameters:

1. **ClientHello** → Client offers supported ciphers, TLS versions, and extensions
2. **ServerHello** → Server selects cipher, version, and confirms capabilities
3. **Certificate** → Server proves identity with certificate chain
4. **ServerKeyExchange** → Server sends ECDH parameters for key agreement
5. **ServerHelloDone** → Marks end of server's initial messages

### Phase 2: Key Derivation
Both sides generate the shared secret:

6. **ClientKeyExchange** → Client sends ECDH public key
   - Both sides compute: `PreMasterSecret = ECDH(client_key, server_key)`
   - Both derive: `MasterSecret = PRF(PreMasterSecret, "master secret", client_random || server_random)`
   - Both generate session encryption/decryption keys

### Phase 3: Cipher Activation & Verification
Both sides verify the handshake and activate encryption:

7. **ChangeCipherSpec (Client)** → "Start encrypting from now on"
8. **Finished (Client)** → Encrypted MAC of all handshake messages
9. **NewSessionTicket (Server)** → Optional ticket for future session resumption
10. **ChangeCipherSpec (Server)** → "Start encrypting from now on"
11. **Finished (Server)** → Encrypted MAC of all handshake messages

### Phase 4: Secure Communication
Application data is now encrypted using the derived session keys.

---

## Complete Working Example

Here's the complete Java code that produces the above handshake:

### App.java - Main Application

```java
/*
 * This source file was generated by the Gradle 'init' task
 */
package ssl.debug;

import ssl.debug.adapter.BasicHttpsClient;

public class App {

    BasicHttpsClient basicHttpsClient = new BasicHttpsClient();

    public String getGreeting() throws Exception {
        return basicHttpsClient.get("https://httpbin.org/get");
    }

    public String getSslInfo() throws Exception {
        return basicHttpsClient.getSSLInfo("https://httpbin.org/get");
    }

    public static void main(String[] args) throws Exception {
        System.setProperty("javax.net.debug", "all");
        App app = new App();
        System.out.println(app.getGreeting());
        System.out.println(app.getSslInfo());
    }
}
```

### BasicHttpsClient.java - HTTPS Client Library

```java
package ssl.debug.adapter;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * A simple HTTPS client for making secure HTTP requests.
 * Useful for demonstrating SSL/TLS handshake and debugging.
 */
public class BasicHttpsClient {
    
    private String userAgent = "Mozilla/5.0 (Java SSL Debug)";
    private int connectionTimeout = 10000; // 10 seconds
    private int readTimeout = 10000; // 10 seconds
    
    /**
     * Performs a GET request to the specified URL
     * 
     * @param urlString The HTTPS URL to connect to
     * @return The response body as a string
     * @throws Exception if connection fails
     */
    public String get(String urlString) throws Exception {
        return get(urlString, null);
    }
    
    /**
     * Performs a GET request with custom headers
     * 
     * @param urlString The HTTPS URL to connect to
     * @param headers Custom headers to send (can be null)
     * @return The response body as a string
     * @throws Exception if connection fails
     */
    public String get(String urlString, Map<String, String> headers) throws Exception {
        HttpURLConnection connection = null;
        try {
            URL url = new URI(urlString).toURL();
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(connectionTimeout);
            connection.setReadTimeout(readTimeout);
            connection.setRequestProperty("User-Agent", userAgent);

            // Add custom headers if provided
            if (headers != null) {
                headers.forEach(connection::setRequestProperty);
            }

            int responseCode = connection.getResponseCode();
            String responseBody = readResponse(connection);

            System.out.println("Status Code: " + responseCode);
            System.out.println("Response Headers:");
            printHeaders(connection.getHeaderFields());
            
            return responseBody;
            
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
    
    /**
     * Gets SSL/TLS information from the connection
     * 
     * @param urlString The HTTPS URL to connect to
     * @return SSL information as a formatted string
     * @throws Exception if connection fails
     */
    public String getSSLInfo(String urlString) throws Exception {
        HttpURLConnection connection = null;
        try {
            URL url = new URI(urlString).toURL();
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("HEAD");
            connection.setConnectTimeout(connectionTimeout);
            connection.setReadTimeout(readTimeout);
            
            // Trigger connection to perform SSL handshake
            connection.getResponseCode();
            
            StringBuilder info = new StringBuilder();
            info.append("=== SSL/TLS Information ===\n");
            info.append("URL: ").append(urlString).append("\n");
            info.append("Status Code: ").append(connection.getResponseCode()).append("\n");
            info.append("Content Type: ").append(connection.getContentType()).append("\n");
            
            // Get cipher suite if available (only on HTTPS)
            if (connection instanceof javax.net.ssl.HttpsURLConnection httpsConnection) {
                httpsConnection.connect();
                info.append("Cipher Suite: ").append(httpsConnection.getCipherSuite()).append("\n");
                var sslSession = httpsConnection.getSSLSession();
                if (sslSession.isPresent()) {
                    info.append("Protocol: ").append(sslSession.get().getProtocol()).append("\n");
                }
            }
            
            return info.toString();
            
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
    
    /**
     * Reads the response body from the connection
     */
    private String readResponse(HttpURLConnection connection) throws Exception {
        StringBuilder response = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line).append("\n");
            }
        }
        return response.toString();
    }
    
    /**
     * Prints response headers
     */
    private void printHeaders(Map<String, List<String>> headers) {
        headers.forEach((key, values) -> {
            if (key != null) { // null key is the status line
                values.forEach(value -> System.out.println("  " + key + ": " + value));
            }
        });
    }
    
    /**
     * Sets the connection timeout
     */
    public void setConnectionTimeout(int timeout) {
        this.connectionTimeout = timeout;
    }
    
    /**
     * Sets the read timeout
     */
    public void setReadTimeout(int timeout) {
        this.readTimeout = timeout;
    }
    
    /**
     * Sets the User-Agent header
     */
    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }
}
```

---

## How to Run the Example

### Prerequisites
- Java 21+ (or JDK 17+ minimum)
- Gradle 8.x

### Steps

1. **Clone or download the project**:
```bash
cd ssl-debug
```

2. **Build the project**:
```bash
./gradlew build
```

3. **Run with SSL debugging enabled**:
```bash
./gradlew run
```

4. **Capture output to file**:
```bash
./gradlew run > all.log 2>&1
```

The application will:
- Enable `javax.net.debug=all`
- Make a GET request to `https://httpbin.org/get`
- Log all TLS handshake messages
- Display SSL/TLS information (cipher suite, protocol version, etc.)

---

## Key Insights from the Handshake

### Certificate Chain Validation
The SSL/TLS debug output shows the server provided three certificates:
1. **End-Entity Certificate**: httpbin.org (issued by Amazon RSA 2048 M CA)
2. **Intermediate CA**: Amazon RSA 2048 M
3. **Root CA**: Amazon Root CA (trusted in system trust store)

The JVM validates this chain by:
- Loading 152 trusted CA certificates from `cacerts`
- Verifying each certificate's signature against its issuer
- Checking validity dates
- Confirming key usage extensions

### Cipher Suite Selection
The server chose `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` because it's:
- **Secure**: Modern encryption standard
- **Forward-Secure**: Uses ephemeral ECDH keys (PFS)
- **Authenticated**: RSA signature verification
- **Authenticated Encryption**: AES-GCM prevents tampering

### Key Exchange Details
- **Algorithm**: ECDHE (Elliptic Curve Diffie-Hellman Ephemeral)
- **Benefits**: 
  - Perfect Forward Secrecy (PFS) - past sessions are safe even if long-term keys are compromised
  - Faster than traditional DHE
  - Smaller key sizes with equivalent security

---

## Common SSL/TLS Debugging Scenarios

### Debug Level Options
```java
// Full debug (as shown in this guide)
System.setProperty("javax.net.debug", "all");

// Specific areas only
System.setProperty("javax.net.debug", "ssl");           // SSL records
System.setProperty("javax.net.debug", "ssl:handshake"); // Handshake only
System.setProperty("javax.net.debug", "ssl:trustmanager"); // Certificate validation
System.setProperty("javax.net.debug", "ssl:record");    // Record layer details
```

### Troubleshooting Common Issues

**Issue**: `PKIX path building failed`
- **Cause**: Certificate not in trust store
- **Solution**: Import certificate or use custom TrustManager

**Issue**: `Unsupported or unrecognized SSL message`
- **Cause**: Connecting to non-HTTPS endpoint
- **Solution**: Verify URL starts with `https://`

**Issue**: `Excessive network latency`
- **Cause**: Certificate revocation checks (OCSP, CRL)
- **Solution**: Disable revocation checks if acceptable for your use case

---

## Conclusion

Understanding TLS/TLS handshakes is essential for:
- **Debugging**: Identifying connection issues quickly
- **Security**: Understanding what's being negotiated
- **Performance**: Analyzing handshake overhead
- **Compliance**: Verifying secure cipher suites are used

The `javax.net.debug` system property provides deep visibility into Java's SSL/TLS layer, making it invaluable for developers working with secure connections.

**Remember**: The SSL/TLS logs contain sensitive handshake information. Only enable debugging in development environments, never in production without careful security considerations.

---

## Resources

- [Java JSSE Documentation](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html)
- [RFC 5246 - TLS 1.2](https://tools.ietf.org/html/rfc5246)
- [OWASP Transport Layer Protection](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html)
- [httpbin.org](https://httpbin.org/) - HTTP Request/Response Service

---

## Get the Code

The source code for this project is available on GitHub:

**📚 [github.com/mnafshin/ssl-debug](https://github.com/mnafshin/ssl-debug)**

Feel free to fork, clone, and experiment with the examples to deepen your understanding of SSL/TLS handshakes!
