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.