Skip to content

Commit

Permalink
reverseproxy: add tls_server_cert_sha256
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
akovalenko committed May 20, 2024
1 parent db3e19b commit 44be7cc
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 0 deletions.
11 changes: 11 additions & 0 deletions modules/caddyhttp/reverseproxy/caddyfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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 {
Expand Down
42 changes: 42 additions & 0 deletions modules/caddyhttp/reverseproxy/httptransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -506,6 +509,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"`

Expand Down Expand Up @@ -640,6 +648,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]
Expand Down Expand Up @@ -727,6 +743,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)
Expand Down

0 comments on commit 44be7cc

Please sign in to comment.