Troubleshooting A TLS Connection

I was trying to create a TLS client over tcp for a third party service. So I created the following sample code to test.

import (
	"crypto/tls"
	"fmt"
	"os"
)

func main() {
	conn, err := tls.Dial("tcp", os.Args[1], &tls.Config{})
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to open connection: %v\n", err)
		os.Exit(1)
	}
	defer conn.Close()

	message := "Hello from TLS client!"
	_, err = conn.Write([]byte(message))
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to write to server: %v\n", err)
		os.Exit(1)
	}
	fmt.Println("Sent:", message)
	buffer := make([]byte, 1024)
	n, err := conn.Read(buffer)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to write to server: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("Received: %s\n", string(buffer[:n]))
}

After building the binary and running it I got this weird error:

    $ go build -o client main.go
    $ ./client supersecretserver.com:21050
    Error opening socket to supersecretserver.com:21050, remote error: tls: handshake failure
    

I tried another way by using openssl as client and There was no issue, Which means the issue is in my code and not the server itself.

The error message doesn’t give much context about what’s wrong, So I decided to capture the network traffic so I can analyze it Wireshark.

$ sudo tcpdump -i ens192 -s 65535 -w out.pcap
dropped privs to tcpdump
tcpdump: listening on ens192, link-type EN10MB (Ethernet), capture size 65535 bytes
562 packets captured
571 packets received by filter
0 packets dropped by kernel

Now All I have to do is open the file out.pcap in Wireshark.

After filtering a bunch of traffic I found the tcp stream I am interested in.

The Client is sending a “Client Hello” packet but it’s recieving an error and the TLS handshake doesn’t happen.

For the openssl client things work as intended.

The issue must be in the “Client Hello” packet. The server doesn’t like whatever the Go client is sending.

I open the details for both packets side by side so I can do a comparison. On the left is the one send by the Go client, and on the right is the one sent by openssl

The thing that stands out is that the packet from the Go client is bigger and the number of ciphers offered is less than what openssl is offering. Let’s check the response from the server when using openssl.

Looks like the server is choosing the cipher suite called TLS_RSA_WITH_AES_256_GCM_SHA384. Let’s see if the Go client has this cipher suite in the list of suggestions.

Interesting ! Looks like the Go client isn’t offering this particular cipher suite. If the server can only work with TLS_RSA_WITH_AES_256_GCM_SHA384 that would explain why it’s responding with an error. To test this hypothesis, I just have to change the code so that it would offer this cipher suite.

import (
	"crypto/tls"
	"fmt"
	"os"
)

func main() {
	conn, err := tls.Dial("tcp", os.Args[1], &tls.Config{
		CipherSuites: []uint16{tls.TLS_RSA_WITH_AES_256_GCM_SHA384},
	})
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to open connection: %v\n", err)
		os.Exit(1)
	}
	defer conn.Close()

	message := "Hello from TLS client!"
	_, err = conn.Write([]byte(message))
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to write to server: %v\n", err)
		os.Exit(1)
	}
	fmt.Println("Sent:", message)
	buffer := make([]byte, 1024)
	n, err := conn.Read(buffer)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to write to server: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("Received: %s\n", string(buffer[:n]))
}

Now let’s build and test

$ go build -o client main.go
$ ./client supersecretserver.com:21050
Sent: "Hello from TLS client!"
Received: 

Looks like the hypothesis was right. After doing a small search I found that GO 1.22 marks a list of cipher suites as insecure and doesn’t offer them during a TLS handshake.

Below is the excerpt from the GO 1.22 release notes.

By default, cipher suites without ECDHE support are no longer offered by either clients or servers during pre-TLS 1.3 handshakes. This change can be reverted with the tlsrsakex=1 GODEBUG setting.