fix(outbounds): use hostname for HTTPS proxy SNI (#1597)
The httpOutbound.dial() method incorrectly sets tls.Config.ServerName to o.Addr, which is the "host:port" address string. TLS SNI must be a pure hostname without a port number. Sending "host:port" as SNI causes invalid TLS ClientHello messages and may prevent the proxy server from correctly routing the connection. Use o.ServerName instead, which is already correctly set to u.Hostname() in NewHTTPOutbound.
This commit is contained in:
parent
f4c9de56cc
commit
829d125ea2
2 changed files with 89 additions and 1 deletions
|
|
@ -88,7 +88,7 @@ func (o *httpOutbound) dial() (net.Conn, error) {
|
|||
// Wrap the connection with TLS if the proxy is HTTPS.
|
||||
conn = tls.Client(conn, &tls.Config{
|
||||
InsecureSkipVerify: o.Insecure,
|
||||
ServerName: o.Addr,
|
||||
ServerName: o.ServerName,
|
||||
})
|
||||
}
|
||||
return conn, nil
|
||||
|
|
|
|||
88
extras/outbounds/ob_http_test.go
Normal file
88
extras/outbounds/ob_http_test.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package outbounds
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHTTPOutboundHTTPSUsesConfiguredServerName(t *testing.T) {
|
||||
cert, err := newHTTPOutboundTestCert()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
gotSNI := make(chan string, 1)
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
tlsConn := tls.Server(conn, &tls.Config{
|
||||
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
gotSNI <- hello.ServerName
|
||||
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
|
||||
},
|
||||
})
|
||||
_ = tlsConn.Handshake()
|
||||
}()
|
||||
|
||||
outbound := &httpOutbound{
|
||||
Dialer: &net.Dialer{Timeout: time.Second},
|
||||
Addr: ln.Addr().String(),
|
||||
HTTPS: true,
|
||||
Insecure: true,
|
||||
ServerName: "proxy.example.com",
|
||||
}
|
||||
conn, err := outbound.dial()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
if err := conn.(*tls.Conn).Handshake(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case got := <-gotSNI:
|
||||
if got != outbound.ServerName {
|
||||
t.Fatalf("SNI = %q, want %q", got, outbound.ServerName)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out waiting for ClientHello")
|
||||
}
|
||||
}
|
||||
|
||||
func newHTTPOutboundTestCert() (tls.Certificate, error) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{CommonName: "localhost"},
|
||||
NotBefore: time.Now().Add(-time.Hour),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
DNSNames: []string{"localhost"},
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key}, nil
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue