reverseproxy: add tls_server_cert_sha256

Unfortunately there *are* some production setups requiring
tls_insecure_skip_verify in reverse_proxy, like old devices with
outdated firmware. In many such cases, the devices aren't supposed to
regenerate or update their certificates.

This patch adds tls_server_cert_sha256 directive for reverse_proxy,
making MITM impossible even with tls_insecure_skip_verify.
This commit is contained in:
Anton Kovalenko 2024-05-20 19:32:15 +03:00
parent f6d2c293e7
commit 5fde819284
2 changed files with 53 additions and 0 deletions

View file

@ -938,6 +938,7 @@ func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error
// tls
// tls_client_auth <automate_name> | <cert_file> <key_file>
// tls_insecure_skip_verify
// tls_server_cert_sha256 <fingerprint>
// tls_timeout <duration>
// tls_trusted_ca_certs <cert_files...>
// tls_server_name <sni>
@ -1101,6 +1102,16 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
h.TLS.InsecureSkipVerify = true
case "tls_server_cert_sha256":
args := d.RemainingArgs()
if len(args) != 1 {
return d.ArgErr()
}
if h.TLS == nil {
h.TLS = new(TLSConfig)
}
h.TLS.ServerCertSha256 = args[0]
case "tls_curves":
args := d.RemainingArgs()
if len(args) == 0 {

View file

@ -15,10 +15,13 @@
package reverseproxy
import (
"bytes"
"context"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
weakrand "math/rand"
@ -528,6 +531,11 @@ type TLSConfig struct {
// option except in testing or local development environments.
InsecureSkipVerify bool `json:"insecure_skip_verify,omitempty"`
// If non-empty, TLS compares the SHA-256 fingerprint of the
// server certificate to a fixed value, specified as
// hexadecimal string.
ServerCertSha256 string `json:"server_cert_sha256,omitempty"`
// The duration to allow a TLS handshake to a server. Default: No timeout.
HandshakeTimeout caddy.Duration `json:"handshake_timeout,omitempty"`
@ -662,6 +670,14 @@ func (t *TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error)
// throw all security out the window
cfg.InsecureSkipVerify = t.InsecureSkipVerify
if t.ServerCertSha256 != "" {
verifier, err := makeFixedCertVerifier(t.ServerCertSha256)
if err != nil {
return nil, err
}
cfg.VerifyPeerCertificate = verifier
}
curvesAdded := make(map[tls.CurveID]struct{})
for _, curveName := range t.Curves {
curveID := caddytls.SupportedCurves[curveName]
@ -749,6 +765,32 @@ func sliceContains(haystack []string, needle string) bool {
return false
}
func makeFixedCertVerifier(fingerprint string) (
func([][]byte, [][]*x509.Certificate) error, error) {
fpHex := strings.ReplaceAll(fingerprint, ":", "")
fpBytes, err := hex.DecodeString(fpHex)
if err != nil {
return nil, err
}
if len(fpBytes) != 32 {
return nil, fmt.Errorf(
"sha256 fingerprint expected to be 32 bytes, got %v",
len(fpBytes))
}
errWrongCert := fmt.Errorf("fixed certificate expected: sha256=%v",
fingerprint)
return func(certs [][]byte, vchain [][]*x509.Certificate) error {
if len(certs) < 1 {
return errWrongCert
}
certFp := sha256.Sum256(certs[0])
if !bytes.Equal(fpBytes, certFp[:]) {
return errWrongCert
}
return nil
}, nil
}
// Interface guards
var (
_ caddy.Provisioner = (*HTTPTransport)(nil)