Add support for certificate fingerprint pinning
This commit is contained in:
parent
ce4e23e5d8
commit
151e7cf586
|
@ -1,7 +1,11 @@
|
||||||
package tlstunnel
|
package tlstunnel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -130,6 +134,49 @@ func parseBackend(backend *Backend, d *scfg.Directive) error {
|
||||||
return fmt.Errorf("failed to setup backend %q: unsupported URI scheme", backendURI)
|
return fmt.Errorf("failed to setup backend %q: unsupported URI scheme", backendURI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, child := range d.Children {
|
||||||
|
switch child.Name {
|
||||||
|
case "tls_certfp":
|
||||||
|
if backend.TLSConfig == nil {
|
||||||
|
return fmt.Errorf("tls_certfp requires a tls:// backend address")
|
||||||
|
}
|
||||||
|
|
||||||
|
var algo, wantCertFP string
|
||||||
|
if err := child.ParseParams(&algo, &wantCertFP); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if algo != "sha-256" {
|
||||||
|
return fmt.Errorf("directive tls_certfp: only sha-256 is supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
wantCertFP = strings.ReplaceAll(wantCertFP, ":", "")
|
||||||
|
wantSum, err := hex.DecodeString(wantCertFP)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("directive tls_certfp: invalid fingerprint: %v", err)
|
||||||
|
} else if len(wantSum) != sha256.Size {
|
||||||
|
return fmt.Errorf("directive tls_certfp: invalid fingerprint length")
|
||||||
|
}
|
||||||
|
|
||||||
|
backend.TLSConfig.InsecureSkipVerify = true
|
||||||
|
backend.TLSConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
|
if len(rawCerts) == 0 {
|
||||||
|
return fmt.Errorf("the server didn't present any TLS certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rawCert := range rawCerts {
|
||||||
|
sum := sha256.Sum256(rawCert)
|
||||||
|
if subtle.ConstantTimeCompare(sum[:], wantSum) == 1 {
|
||||||
|
return nil // fingerprints match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := sha256.Sum256(rawCerts[0])
|
||||||
|
remoteCertFP := hex.EncodeToString(sum[:])
|
||||||
|
return fmt.Errorf("configured TLS certificate fingerprint doesn't match the server's - %s", remoteCertFP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,20 @@ The following directives are supported:
|
||||||
The _+proxy_ suffix can be added to the URI scheme to forward
|
The _+proxy_ suffix can be added to the URI scheme to forward
|
||||||
connection metadata via the PROXY protocol.
|
connection metadata via the PROXY protocol.
|
||||||
|
|
||||||
|
The backend directive supports the following sub-directives:
|
||||||
|
|
||||||
|
*tls_certfp* sha-256 <fingerprint>
|
||||||
|
Instead of using CAs to check the TLS certificate provided by the
|
||||||
|
backend, check that the certificate matches the provided
|
||||||
|
fingerprint. This can be used to connect to servers with a
|
||||||
|
self-signed certificate, for instance.
|
||||||
|
|
||||||
|
The fingerprint of a certificate can be obtained via *openssl*(1):
|
||||||
|
|
||||||
|
```
|
||||||
|
openssl x509 -fingerprint -sha256 -noout <certificate>
|
||||||
|
```
|
||||||
|
|
||||||
*tls* { ... }
|
*tls* { ... }
|
||||||
Customise frontend-specific TLS configuration.
|
Customise frontend-specific TLS configuration.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue