diff --git a/chain.go b/chain.go index 306c697..f4dc6dd 100644 --- a/chain.go +++ b/chain.go @@ -3,13 +3,11 @@ package gost import ( "crypto/rand" "crypto/tls" + "crypto/x509" "encoding/base64" "errors" - "github.com/ginuerzh/pht" - "github.com/golang/glog" - "github.com/lucas-clemente/quic-go/h2quic" - "golang.org/x/net/http2" "io" + "io/ioutil" "net" "net/http" "net/http/httputil" @@ -18,6 +16,11 @@ import ( "strings" "sync" "time" + + "github.com/ginuerzh/pht" + "github.com/golang/glog" + "github.com/lucas-clemente/quic-go/h2quic" + "golang.org/x/net/http2" ) // Proxy chain holds a list of proxy nodes @@ -93,6 +96,22 @@ func (c *ProxyChain) Init() { InsecureSkipVerify: node.insecureSkipVerify(), ServerName: node.serverName, } + + caFile := node.caFile() + + if caFile != "" { + cfg.RootCAs = x509.NewCertPool() + + data, err := ioutil.ReadFile(caFile) + if err != nil { + glog.Fatal(err) + } + + if !cfg.RootCAs.AppendCertsFromPEM(data) { + glog.Fatal(err) + } + } + c.http2NodeIndex = i c.initHttp2Client(cfg, c.nodes[:i]...) break // shortest chain for HTTP2 @@ -152,6 +171,68 @@ func (c *ProxyChain) Http2Enabled() bool { return c.http2Enabled } +// Wrap a net.Conn into a client tls connection, performing any +// additional verification as needed. +// +// As of go 1.3, crypto/tls only supports either doing no certificate +// verification, or doing full verification including of the peer's +// DNS name. For consul, we want to validate that the certificate is +// signed by a known CA, but because consul doesn't use DNS names for +// node names, we don't verify the certificate DNS names. Since go 1.3 +// no longer supports this mode of operation, we have to do it +// manually. +// +// This code is taken from consul: +// https://github.com/hashicorp/consul/blob/master/tlsutil/config.go +func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) { + var err error + var tlsConn *tls.Conn + + tlsConn = tls.Client(conn, tlsConfig) + + // If crypto/tls is doing verification, there's no need to do our own. + if tlsConfig.InsecureSkipVerify == false { + return tlsConn, nil + } + + // Similarly if we use host's CA, we can do full handshake + if tlsConfig.RootCAs == nil { + return tlsConn, nil + } + + // Otherwise perform handshake, but don't verify the domain + // + // The following is lightly-modified from the doFullHandshake + // method in https://golang.org/src/crypto/tls/handshake_client.go + if err = tlsConn.Handshake(); err != nil { + tlsConn.Close() + return nil, err + } + + opts := x509.VerifyOptions{ + Roots: tlsConfig.RootCAs, + CurrentTime: time.Now(), + DNSName: "", + Intermediates: x509.NewCertPool(), + } + + certs := tlsConn.ConnectionState().PeerCertificates + for i, cert := range certs { + if i == 0 { + continue + } + opts.Intermediates.AddCert(cert) + } + + _, err = certs[0].Verify(opts) + if err != nil { + tlsConn.Close() + return nil, err + } + + return tlsConn, err +} + func (c *ProxyChain) initHttp2Client(config *tls.Config, nodes ...ProxyNode) { if c.http2NodeIndex < 0 || c.http2NodeIndex >= len(c.nodes) { return @@ -166,7 +247,11 @@ func (c *ProxyChain) initHttp2Client(config *tls.Config, nodes ...ProxyNode) { if err != nil { return conn, err } - conn = tls.Client(conn, cfg) + + conn, err = wrapTLSClient(conn, cfg) + if err != nil { + return conn, err + } // enable HTTP2 ping-pong pingIntvl, _ := strconv.Atoi(http2Node.Get("ping")) diff --git a/node.go b/node.go index f9398f5..451211c 100644 --- a/node.go +++ b/node.go @@ -3,12 +3,13 @@ package gost import ( "bufio" "fmt" - "github.com/golang/glog" "net" "net/url" "os" "strconv" "strings" + + "github.com/golang/glog" ) // Proxy node represent a proxy @@ -142,6 +143,10 @@ func (node *ProxyNode) insecureSkipVerify() bool { return !node.getBool("secure") } +func (node *ProxyNode) caFile() string { + return node.Get("ca") +} + func (node *ProxyNode) certFile() string { if cert := node.Get("cert"); cert != "" { return cert