Man In The Middle in Chi with Mutual Tls
Man In The Middle in Chi with Mutual Tls — how this specific combination creates or exposes the vulnerability
In Chi, a Man In The Middle (MitM) scenario with Mutual TLS (mTLS) arises when identity assurance is incomplete or when mTLS is not enforced for all endpoints. Even though both client and server present certificates, an attacker who can intercept traffic and present a valid certificate accepted by the server (or a valid client certificate accepted by the server) can sit in the middle. This commonly occurs in Chi applications when developers configure mTLS but omit strict verification of client certificates or rely on default trust stores that include overly permissive roots. In Chi, the server-side configuration typically uses tls.Config with ClientCAs and ClientAuth set to VerifyClientCertIfGiven or VerifyClientCertRequired; if these are not set strictly, an attacker can use a certificate signed by an accepted CA to relay requests, undermining the intended mutual authentication.
Another common vector in Chi is mismatched hostname verification or missing certificate pinning, allowing an attacker with a valid certificate (possibly obtained via compromise or a rogue CA) to terminate the connection on the server side and re-encrypt with the server using a different certificate. Because Chi’s HTTP server does not enforce certificate transparency or hostname checks by default, the server may accept the attacker’s certificate and continue the session, exposing request and response bodies. This is especially risky when services communicate over localhost or internal networks where encryption is assumed but mTLS is inconsistently applied.
Additionally, if a Chi service accepts both HTTP and HTTPS and redirects HTTP to HTTPS without enforcing mTLS on the HTTPS endpoint, an attacker can force the client to use HTTP (or downgrade the protocol) and intercept cleartext traffic. Even when mTLS is configured, if the client does not validate the server’s certificate chain and hostname, it may accept a certificate presented by an attacker. These issues map to OWASP API Security Top 10 controls around authentication and encryption, and they highlight why mTLS in Chi must be implemented with rigorous verification on both sides to prevent MitM.
Mutual Tls-Specific Remediation in Chi — concrete code fixes
To remediate MitM risks in Chi when using mTLS, enforce strict client certificate verification on the server and validate server certificates on the client. Below are concrete, working examples for both server and client configurations.
Server-side (strict mTLS)
Configure the Chi server to require and verify client certificates, and to check that the certificate’s Common Name or SANs match expected identities. Use a dedicated CA pool that contains only the trusted CAs.
// server.go
package main
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
// Load server certificate and key
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
panic(err)
}
// Load and parse the CA that issued client certificates
caCert, err := ioutil.ReadFile("ca.crt")
if err != nil {
panic(err)
}
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(caCert) {
panic("failed to add CA cert")
}
// Configure TLS with RequireAndVerifyClientCert
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: false,
}
r := chi.NewRouter()
r.Get("/secure", func(w http.ResponseWriter, r *http.Request) {
// At this point, client certificate is verified by Go TLS layer
w.Write([]byte("OK"))
})
srv := &http.Server{
Addr: ":8443",
TLSConfig: tlsConfig,
Handler: r,
}
srv.ListenAndServeTLS("", "")
}
Client-side (verify server certificate)
The client must validate the server’s certificate chain and hostname. Use a custom tls.Config with RootCAs and set ServerName for hostname verification.
// client.go
package main
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net/http"
)
func main() {
// Load client certificate and key
cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
if err != nil {
panic(err)
}
// Load server CA to verify server certificate
caCert, err := ioutil.ReadFile("ca.crt")
if err != nil {
panic(err)
}
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(caCert) {
panic("failed to add CA cert")
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
ServerName: "api.example.com",
InsecureSkipVerify: false,
}
transport := &http.Transport{
TLSClientConfig: tlsConfig,
}
client := &http.Client{Transport: transport}
resp, err := client.Get("https://api.example.com:8443/secure")
if err != nil {
panic(err)
}
defer resp.Body.Close()
}
These configurations ensure that both sides validate each other’s certificates, reducing the risk of a MitM in Chi deployments. For ongoing security, rotate certificates and restrict the CA pool to only necessary issuers.
Frequently Asked Questions
What should I do if a client certificate is optional but I still want to prevent MitM?
ClientAuth to VerifyClientCertIfGiven and enforce additional checks at the application layer for sensitive endpoints. Consider upgrading to RequireAndVerifyClientCert where feasible to ensure strict mTLS.