How to Use the crypto/tls Package for Custom TLS Configurations

You use the `crypto/tls` package by creating a `tls.Config` struct, customizing fields like `RootCAs` or `InsecureSkipVerify`, and passing it to `tls.Dial` or `http.Transport`.

You use the crypto/tls package by creating a tls.Config struct, customizing fields like RootCAs or InsecureSkipVerify, and passing it to tls.Dial or http.Transport. This allows you to enforce specific certificate validation rules, enable mutual TLS (mTLS), or configure cipher suites beyond the defaults.

Here is a practical example of creating a custom client that validates a server certificate against a specific CA file and requires client authentication (mTLS):

package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

func main() {
	// Load the CA certificate used to verify the server
	caCert, err := ioutil.ReadFile("ca-cert.pem")
	if err != nil {
		panic(err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	// Load client certificate and key for mTLS
	clientCert, err := tls.LoadX509KeyPair("client-cert.pem", "client-key.pem")
	if err != nil {
		panic(err)
	}

	// Create custom TLS config
	tlsConfig := &tls.Config{
		RootCAs:            caCertPool, // Only trust this specific CA
		Certificates:       []tls.Certificate{clientCert}, // Present client cert
		MinVersion:         tls.VersionTLS12, // Enforce minimum TLS version
		ServerName:         "api.example.com", // SNI hostname
		InsecureSkipVerify: false, // Never set to true in production
	}

	// Configure HTTP client to use the custom TLS config
	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: tlsConfig,
		},
		Timeout: 10 * time.Second,
	}

	resp, err := client.Get("https://api.example.com/secure-endpoint")
	if err != nil {
		fmt.Println("Request failed:", err)
		return
	}
	defer resp.Body.Close()

	fmt.Println("Status:", resp.Status)
}

For a server-side example, you can customize the tls.Config to require client certificates and restrict supported cipher suites:

package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	// Load CA for client verification
	caCert, _ := ioutil.ReadFile("client-ca.pem")
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	// Load server certificate
	cert, _ := tls.LoadX509KeyPair("server-cert.pem", "server-key.pem")

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientAuth:   tls.RequireAndVerifyClientCert, // Force mTLS
		ClientCAs:    caCertPool,                      // Verify client against this CA
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		},
		MinVersion: tls.VersionTLS12,
	}

	server := &http.Server{
		Addr:      ":8443",
		TLSConfig: tlsConfig,
		Handler:   http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprintf(w, "Secure connection established")
		}),
	}

	fmt.Println("Starting server on :8443")
	if err := server.ListenAndServeTLS("", ""); err != nil {
		fmt.Println("Server error:", err)
	}
}

Always ensure you load certificates from trusted sources and avoid setting InsecureSkipVerify to true in production, as it bypasses all certificate validation. Use ServerName in client configs to match the Common Name (CN) or Subject Alternative Name (SAN) on the server certificate to prevent man-in-the-middle attacks.