Просмотр исходного кода

Fix upstream proxy support for "Proxy-Connection: close"

Rod Hynes 5 лет назад
Родитель
Сommit
7d4307b1a3

+ 7 - 1
psiphon/controller_test.go

@@ -42,6 +42,7 @@ import (
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/quic"
 	"github.com/elazarl/goproxy"
+	"github.com/elazarl/goproxy/ext/auth"
 )
 
 func TestMain(m *testing.M) {
@@ -1039,7 +1040,7 @@ func initDisruptor() {
 	}()
 }
 
-const upstreamProxyURL = "http://127.0.0.1:2161"
+const upstreamProxyURL = "http://testUser:testPassword@127.0.0.1:2161"
 
 var upstreamProxyCustomHeaders = map[string][]string{"X-Test-Header-Name": {"test-header-value1", "test-header-value2"}}
 
@@ -1063,6 +1064,11 @@ func initUpstreamProxy() {
 		proxy := goproxy.NewProxyHttpServer()
 		proxy.Logger = log.New(ioutil.Discard, "", 0)
 
+		auth.ProxyBasic(
+			proxy,
+			"testRealm",
+			func(user, passwd string) bool { return user == "testUser" && passwd == "testPassword" })
+
 		proxy.OnRequest().DoFunc(
 			func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
 				if !hasExpectedCustomHeaders(r.Header) {

+ 16 - 0
psiphon/upstreamproxy/proxy_http.go

@@ -219,6 +219,22 @@ func (pc *proxyConn) handshake(addr, username, password string) error {
 				return err
 			}
 		}
+
+		headers := resp.Header[http.CanonicalHeaderKey("proxy-connection")]
+		for _, header := range headers {
+			if header == "close" {
+				// The server has signaled that it will close the
+				// connection. Create a new ClientConn and continue the
+				// handshake.
+				err = pc.makeNewClientConn()
+				if err != nil {
+					// Already wrapped in proxyError
+					return err
+				}
+				break
+			}
+		}
+
 		return nil
 	}
 	pc.authState = HTTP_AUTH_STATE_FAILURE

+ 59 - 12
vendor/github.com/elazarl/goproxy/README.md

@@ -2,14 +2,15 @@
 
 [![GoDoc](https://godoc.org/github.com/elazarl/goproxy?status.svg)](https://godoc.org/github.com/elazarl/goproxy)
 [![Join the chat at https://gitter.im/elazarl/goproxy](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/elazarl/goproxy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+![Status](https://github.com/elazarl/goproxy/workflows/Go/badge.svg)
 
 Package goproxy provides a customizable HTTP proxy library for Go (golang),
 
 It supports regular HTTP proxy, HTTPS through CONNECT, and "hijacking" HTTPS
 connection using "Man in the Middle" style attack.
 
-The intent of the proxy, is to be usable with reasonable amount of traffic
-yet, customizable and programmable.
+The intent of the proxy is to be usable with reasonable amount of traffic,
+yet customizable and programmable.
 
 The proxy itself is simply a `net/http` handler.
 
@@ -22,7 +23,7 @@ For example, the URL you should use as proxy when running `./bin/basic` is
 
 ## Mailing List
 
-New features would be discussed on the [mailing list](https://groups.google.com/forum/#!forum/goproxy-dev)
+New features will be discussed on the [mailing list](https://groups.google.com/forum/#!forum/goproxy-dev)
 before their development.
 
 ## Latest Stable Release
@@ -32,13 +33,13 @@ Get the latest goproxy from `gopkg.in/elazarl/goproxy.v1`.
 # Why not Fiddler2?
 
 Fiddler is an excellent software with similar intent. However, Fiddler is not
-as customizable as goproxy intend to be. The main difference is, Fiddler is not
+as customizable as goproxy intends to be. The main difference is, Fiddler is not
 intended to be used as a real proxy.
 
 A possible use case that suits goproxy but
-not Fiddler, is, gathering statistics on page load times for a certain website over a week.
+not Fiddler, is gathering statistics on page load times for a certain website over a week.
 With goproxy you could ask all your users to set their proxy to a dedicated machine running a
-goproxy server. Fiddler is a GUI app not designed to be ran like a server for multiple users.
+goproxy server. Fiddler is a GUI app not designed to be run like a server for multiple users.
 
 # A taste of goproxy
 
@@ -90,16 +91,62 @@ proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc(
 })
 ```
 
-`DstHostIs` returns a `ReqCondition`, that is a function receiving a `Request` and returning a boolean
-we will only process requests that matches the condition. `DstHostIs("www.reddit.com")` will return
+`DstHostIs` returns a `ReqCondition`, that is a function receiving a `Request` and returning a boolean.
+We will only process requests that match the condition. `DstHostIs("www.reddit.com")` will return
 a `ReqCondition` accepting only requests directed to "www.reddit.com".
 
 `DoFunc` will receive a function that will preprocess the request. We can change the request, or
-return a response. If the time is between 8:00am and 17:00pm, we will neglect the request, and
+return a response. If the time is between 8:00am and 17:00pm, we will reject the request, and
 return a precanned text response saying "do not waste your time".
 
 See additional examples in the examples directory.
 
+
+# Type of handlers for manipulating connect/req/resp behavior
+
+There are 3 kinds of useful handlers to manipulate the behavior, as follows:
+
+```go
+// handler called after receiving HTTP CONNECT from the client, and before proxy establish connection 
+// with destination host
+httpsHandlers   []HttpsHandler
+    
+// handler called before proxy send HTTP request to destination host
+reqHandlers     []ReqHandler 
+    
+// handler called after proxy receives HTTP Response from destination host, and before proxy forward 
+// the Response to the client.
+respHandlers    []RespHandler 
+```
+
+Depending on what you want to manipulate, the ways to add handlers to each handler list are:
+
+```go
+// Add handlers to httpsHandlers 
+proxy.OnRequest(Some ReqConditions).HandleConnect(YourHandlerFunc())
+
+// Add handlers to reqHandlers
+proxy.OnRequest(Some ReqConditions).Do(YourReqHandlerFunc())
+
+// Add handlers to respHandlers
+proxy.OnResponse(Some RespConditions).Do(YourRespHandlerFunc())
+```
+
+For example:
+
+```go
+// This rejects the HTTPS request to *.reddit.com during HTTP CONNECT phase
+proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("reddit.*:443$"))).HandleConnect(goproxy.RejectConnect)
+
+// This will NOT reject the HTTPS request with URL ending with gif, due to the fact that proxy 
+// only got the URL.Hostname and URL.Port during the HTTP CONNECT phase if the scheme is HTTPS, which is
+// quiet common these days.
+proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).HandleConnect(goproxy.RejectConnect)
+
+// The correct way to manipulate the HTTP request using URL.Path as condition is:
+proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).Do(YourReqHandlerFunc())
+```
+
 # What's New
 
 1. Ability to `Hijack` CONNECT requests. See
@@ -108,14 +155,14 @@ See additional examples in the examples directory.
 
 # License
 
-I put the software temporarily under the Go-compatible BSD license,
-if this prevents someone from using the software, do let me know and I'll consider changing it.
+I put the software temporarily under the Go-compatible BSD license.
+If this prevents someone from using the software, do let me know and I'll consider changing it.
 
 At any rate, user feedback is very important for me, so I'll be delighted to know if you're using this package.
 
 # Beta Software
 
-I've received a positive feedback from a few people who use goproxy in production settings.
+I've received positive feedback from a few people who use goproxy in production settings.
 I believe it is good enough for usage.
 
 I'll try to keep reasonable backwards compatibility. In case of a major API change,

+ 6 - 1
vendor/github.com/elazarl/goproxy/counterecryptor.go

@@ -3,6 +3,7 @@ package goproxy
 import (
 	"crypto/aes"
 	"crypto/cipher"
+	"crypto/ecdsa"
 	"crypto/rsa"
 	"crypto/sha256"
 	"crypto/x509"
@@ -21,8 +22,12 @@ func NewCounterEncryptorRandFromKey(key interface{}, seed []byte) (r CounterEncr
 	switch key := key.(type) {
 	case *rsa.PrivateKey:
 		keyBytes = x509.MarshalPKCS1PrivateKey(key)
+	case *ecdsa.PrivateKey:
+		if keyBytes, err = x509.MarshalECPrivateKey(key); err != nil {
+			return
+		}
 	default:
-		err = errors.New("only RSA keys supported")
+		err = errors.New("only RSA and ECDSA keys supported")
 		return
 	}
 	h := sha256.New()

+ 11 - 5
vendor/github.com/elazarl/goproxy/ctx.go

@@ -1,6 +1,7 @@
 package goproxy
 
 import (
+	"crypto/tls"
 	"net/http"
 	"regexp"
 )
@@ -19,14 +20,19 @@ type ProxyCtx struct {
 	// call of RespHandler
 	UserData interface{}
 	// Will connect a request to a response
-	Session int64
-	proxy   *ProxyHttpServer
+	Session   int64
+	certStore CertStorage
+	Proxy     *ProxyHttpServer
 }
 
 type RoundTripper interface {
 	RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error)
 }
 
+type CertStorage interface {
+	Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error)
+}
+
 type RoundTripperFunc func(req *http.Request, ctx *ProxyCtx) (*http.Response, error)
 
 func (f RoundTripperFunc) RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) {
@@ -37,11 +43,11 @@ func (ctx *ProxyCtx) RoundTrip(req *http.Request) (*http.Response, error) {
 	if ctx.RoundTripper != nil {
 		return ctx.RoundTripper.RoundTrip(req, ctx)
 	}
-	return ctx.proxy.Tr.RoundTrip(req)
+	return ctx.Proxy.Tr.RoundTrip(req)
 }
 
 func (ctx *ProxyCtx) printf(msg string, argv ...interface{}) {
-	ctx.proxy.Logger.Printf("[%03d] "+msg+"\n", append([]interface{}{ctx.Session & 0xFF}, argv...)...)
+	ctx.Proxy.Logger.Printf("[%03d] "+msg+"\n", append([]interface{}{ctx.Session & 0xFF}, argv...)...)
 }
 
 // Logf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter
@@ -53,7 +59,7 @@ func (ctx *ProxyCtx) printf(msg string, argv ...interface{}) {
 //		return r, nil
 //	})
 func (ctx *ProxyCtx) Logf(msg string, argv ...interface{}) {
-	if ctx.proxy.Verbose {
+	if ctx.Proxy.Verbose {
 		ctx.printf("INFO: "+msg, argv...)
 	}
 }

+ 16 - 0
vendor/github.com/elazarl/goproxy/dispatcher.go

@@ -161,6 +161,22 @@ func ContentTypeIs(typ string, types ...string) RespCondition {
 	})
 }
 
+// StatusCodeIs returns a RespCondition, testing whether or not the HTTP status
+// code is one of the given ints
+func StatusCodeIs(codes ...int) RespCondition {
+	codeSet := make(map[int]bool)
+	for _, c := range codes {
+		codeSet[c] = true
+	}
+	return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool {
+		if resp == nil {
+			return false
+		}
+		_, codeMatch := codeSet[resp.StatusCode]
+		return codeMatch
+	})
+}
+
 // ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions.
 // You will use the ReqProxyConds struct to register a ReqHandler, that would filter
 // the request, only if all the given ReqCondition matched.

+ 1 - 1
vendor/github.com/elazarl/goproxy/doc.go

@@ -60,7 +60,7 @@ Finally, we have convenience function to throw a quick response
 
 	proxy.OnResponse(hasGoProxyHeader).DoFunc(func(r*http.Response,ctx *goproxy.ProxyCtx)*http.Response {
 		r.Body.Close()
-		return goproxy.ForbiddenTextResponse(ctx.Req,"Can't see response with X-GoProxy header!")
+		return goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusForbidden, "Can't see response with X-GoProxy header!")
 	})
 
 we close the body of the original repsonse, and return a new 403 response with a short message.

+ 79 - 0
vendor/github.com/elazarl/goproxy/ext/auth/basic.go

@@ -0,0 +1,79 @@
+package auth
+
+import (
+	"bytes"
+	"encoding/base64"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	"github.com/elazarl/goproxy"
+)
+
+var unauthorizedMsg = []byte("407 Proxy Authentication Required")
+
+func BasicUnauthorized(req *http.Request, realm string) *http.Response {
+	// TODO(elazar): verify realm is well formed
+	return &http.Response{
+		StatusCode: 407,
+		ProtoMajor: 1,
+		ProtoMinor: 1,
+		Request:    req,
+		Header: http.Header{
+			"Proxy-Authenticate": []string{"Basic realm=" + realm},
+			"Proxy-Connection":   []string{"close"},
+		},
+		Body:          ioutil.NopCloser(bytes.NewBuffer(unauthorizedMsg)),
+		ContentLength: int64(len(unauthorizedMsg)),
+	}
+}
+
+var proxyAuthorizationHeader = "Proxy-Authorization"
+
+func auth(req *http.Request, f func(user, passwd string) bool) bool {
+	authheader := strings.SplitN(req.Header.Get(proxyAuthorizationHeader), " ", 2)
+	req.Header.Del(proxyAuthorizationHeader)
+	if len(authheader) != 2 || authheader[0] != "Basic" {
+		return false
+	}
+	userpassraw, err := base64.StdEncoding.DecodeString(authheader[1])
+	if err != nil {
+		return false
+	}
+	userpass := strings.SplitN(string(userpassraw), ":", 2)
+	if len(userpass) != 2 {
+		return false
+	}
+	return f(userpass[0], userpass[1])
+}
+
+// Basic returns a basic HTTP authentication handler for requests
+//
+// You probably want to use auth.ProxyBasic(proxy) to enable authentication for all proxy activities
+func Basic(realm string, f func(user, passwd string) bool) goproxy.ReqHandler {
+	return goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
+		if !auth(req, f) {
+			return nil, BasicUnauthorized(req, realm)
+		}
+		return req, nil
+	})
+}
+
+// BasicConnect returns a basic HTTP authentication handler for CONNECT requests
+//
+// You probably want to use auth.ProxyBasic(proxy) to enable authentication for all proxy activities
+func BasicConnect(realm string, f func(user, passwd string) bool) goproxy.HttpsHandler {
+	return goproxy.FuncHttpsHandler(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
+		if !auth(ctx.Req, f) {
+			ctx.Resp = BasicUnauthorized(ctx.Req, realm)
+			return goproxy.RejectConnect, host
+		}
+		return goproxy.OkConnect, host
+	})
+}
+
+// ProxyBasic will force HTTP authentication before any request to the proxy is processed
+func ProxyBasic(proxy *goproxy.ProxyHttpServer, realm string, f func(user, passwd string) bool) {
+	proxy.OnRequest().Do(Basic(realm, f))
+	proxy.OnRequest().HandleConnect(BasicConnect(realm, f))
+}

+ 3 - 0
vendor/github.com/elazarl/goproxy/go.mod

@@ -0,0 +1,3 @@
+module github.com/elazarl/goproxy
+
+require github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2

+ 3 - 0
vendor/github.com/elazarl/goproxy/go.sum

@@ -0,0 +1,3 @@
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
+github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=

+ 51 - 12
vendor/github.com/elazarl/goproxy/https.go

@@ -36,6 +36,10 @@ var (
 	httpsRegexp     = regexp.MustCompile(`^https:\/\/`)
 )
 
+// ConnectAction enables the caller to override the standard connect flow.
+// When Action is ConnectHijack, it is up to the implementer to send the
+// HTTP 200, or any other valid http response back to the client from within the
+// Hijack func
 type ConnectAction struct {
 	Action    ConnectActionLiteral
 	Hijack    func(req *http.Request, client net.Conn, ctx *ProxyCtx)
@@ -64,8 +68,16 @@ func (proxy *ProxyHttpServer) connectDial(network, addr string) (c net.Conn, err
 	return proxy.ConnectDial(network, addr)
 }
 
+type halfClosable interface {
+	net.Conn
+	CloseWrite() error
+	CloseRead() error
+}
+
+var _ halfClosable = (*net.TCPConn)(nil)
+
 func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) {
-	ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
+	ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, certStore: proxy.CertStore}
 
 	hij, ok := w.(http.Hijacker)
 	if !ok {
@@ -102,8 +114,8 @@ func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request
 		ctx.Logf("Accepting CONNECT to %s", host)
 		proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
 
-		targetTCP, targetOK := targetSiteCon.(*net.TCPConn)
-		proxyClientTCP, clientOK := proxyClient.(*net.TCPConn)
+		targetTCP, targetOK := targetSiteCon.(halfClosable)
+		proxyClientTCP, clientOK := proxyClient.(halfClosable)
 		if targetOK && clientOK {
 			go copyAndClose(ctx, targetTCP, proxyClientTCP)
 			go copyAndClose(ctx, proxyClientTCP, targetTCP)
@@ -121,8 +133,6 @@ func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request
 		}
 
 	case ConnectHijack:
-		ctx.Logf("Hijacking CONNECT to %s", host)
-		proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
 		todo.Hijack(r, proxyClient, ctx)
 	case ConnectHTTPMitm:
 		proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
@@ -188,7 +198,7 @@ func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request
 			clientTlsReader := bufio.NewReader(rawClientTls)
 			for !isEof(clientTlsReader) {
 				req, err := http.ReadRequest(clientTlsReader)
-				var ctx = &ProxyCtx{Req: req, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
+				var ctx = &ProxyCtx{Req: req, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, UserData: ctx.UserData}
 				if err != nil && err != io.EOF {
 					return
 				}
@@ -209,6 +219,11 @@ func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request
 
 				req, resp := proxy.filterRequest(req, ctx)
 				if resp == nil {
+					if isWebSocketRequest(req) {
+						ctx.Logf("Request looks like websocket upgrade.")
+						proxy.serveWebsocketTLS(ctx, w, req, tlsConfig, rawClientTls)
+						return
+					}
 					if err != nil {
 						ctx.Warnf("Illegal URL %s", "https://"+r.Host+req.URL.Path)
 						return
@@ -293,7 +308,7 @@ func copyOrWarn(ctx *ProxyCtx, dst io.Writer, src io.Reader, wg *sync.WaitGroup)
 	wg.Done()
 }
 
-func copyAndClose(ctx *ProxyCtx, dst, src *net.TCPConn) {
+func copyAndClose(ctx *ProxyCtx, dst, src halfClosable) {
 	if _, err := io.Copy(dst, src); err != nil {
 		ctx.Warnf("Error copying to client: %s", err)
 	}
@@ -314,6 +329,10 @@ func dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (net.Conn,
 }
 
 func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(network, addr string) (net.Conn, error) {
+	return proxy.NewConnectDialToProxyWithHandler(https_proxy, nil)
+}
+
+func (proxy *ProxyHttpServer) NewConnectDialToProxyWithHandler(https_proxy string, connectReqHandler func(req *http.Request)) func(network, addr string) (net.Conn, error) {
 	u, err := url.Parse(https_proxy)
 	if err != nil {
 		return nil
@@ -329,6 +348,9 @@ func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(net
 				Host:   addr,
 				Header: make(http.Header),
 			}
+			if connectReqHandler != nil {
+				connectReqHandler(connectReq)
+			}
 			c, err := proxy.dial(network, u.Host)
 			if err != nil {
 				return nil, err
@@ -355,7 +377,7 @@ func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(net
 			return c, nil
 		}
 	}
-	if u.Scheme == "https" {
+	if u.Scheme == "https" || u.Scheme == "wss" {
 		if strings.IndexRune(u.Host, ':') == -1 {
 			u.Host += ":443"
 		}
@@ -371,6 +393,9 @@ func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(net
 				Host:   addr,
 				Header: make(http.Header),
 			}
+			if connectReqHandler != nil {
+				connectReqHandler(connectReq)
+			}
 			connectReq.Write(c)
 			// Read response.
 			// Okay to use and discard buffered reader here, because
@@ -398,14 +423,28 @@ func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(net
 
 func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCtx) (*tls.Config, error) {
 	return func(host string, ctx *ProxyCtx) (*tls.Config, error) {
-		config := *defaultTLSConfig
+		var err error
+		var cert *tls.Certificate
+
+		hostname := stripPort(host)
+		config := defaultTLSConfig.Clone()
 		ctx.Logf("signing for %s", stripPort(host))
-		cert, err := signHost(*ca, []string{stripPort(host)})
+
+		genCert := func() (*tls.Certificate, error) {
+			return signHost(*ca, []string{hostname})
+		}
+		if ctx.certStore != nil {
+			cert, err = ctx.certStore.Fetch(hostname, genCert)
+		} else {
+			cert, err = genCert()
+		}
+
 		if err != nil {
 			ctx.Warnf("Cannot sign host certificate with provided CA: %s", err)
 			return nil, err
 		}
-		config.Certificates = append(config.Certificates, cert)
-		return &config, nil
+
+		config.Certificates = append(config.Certificates, *cert)
+		return config, nil
 	}
 }

+ 5 - 0
vendor/github.com/elazarl/goproxy/logger.go

@@ -0,0 +1,5 @@
+package goproxy
+
+type Logger interface {
+	Printf(format string, v ...interface{})
+}

+ 60 - 18
vendor/github.com/elazarl/goproxy/proxy.go

@@ -16,9 +16,11 @@ type ProxyHttpServer struct {
 	// session variable must be aligned in i386
 	// see http://golang.org/src/pkg/sync/atomic/doc.go#L41
 	sess int64
+	// KeepDestinationHeaders indicates the proxy should retain any headers present in the http.Response before proxying
+	KeepDestinationHeaders bool
 	// setting Verbose to true will log information on each request sent to the proxy
 	Verbose         bool
-	Logger          *log.Logger
+	Logger          Logger
 	NonproxyHandler http.Handler
 	reqHandlers     []ReqHandler
 	respHandlers    []RespHandler
@@ -27,13 +29,17 @@ type ProxyHttpServer struct {
 	// ConnectDial will be used to create TCP connections for CONNECT requests
 	// if nil Tr.Dial will be used
 	ConnectDial func(network string, addr string) (net.Conn, error)
+	CertStore   CertStorage
+	KeepHeader  bool
 }
 
 var hasPort = regexp.MustCompile(`:\d+$`)
 
-func copyHeaders(dst, src http.Header) {
-	for k, _ := range dst {
-		dst.Del(k)
+func copyHeaders(dst, src http.Header, keepDestHeaders bool) {
+	if !keepDestHeaders {
+		for k := range dst {
+			dst.Del(k)
+		}
 	}
 	for k, vs := range src {
 		for _, v := range vs {
@@ -88,6 +94,16 @@ func removeProxyHeaders(ctx *ProxyCtx, r *http.Request) {
 	//   The Connection general-header field allows the sender to specify
 	//   options that are desired for that particular connection and MUST NOT
 	//   be communicated by proxies over further connections.
+
+	// When server reads http request it sets req.Close to true if
+	// "Connection" header contains "close".
+	// https://github.com/golang/go/blob/master/src/net/http/request.go#L1080
+	// Later, transfer.go adds "Connection: close" back when req.Close is true
+	// https://github.com/golang/go/blob/master/src/net/http/transfer.go#L275
+	// That's why tests that checks "Connection: close" removal fail
+	if r.Header.Get("Connection") == "close" {
+		r.Close = false
+	}
 	r.Header.Del("Connection")
 }
 
@@ -97,7 +113,7 @@ func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	if r.Method == "CONNECT" {
 		proxy.handleHttps(w, r)
 	} else {
-		ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
+		ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy}
 
 		var err error
 		ctx.Logf("Got request %v %v %v %v", r.URL.Path, r.Host, r.Method, r.URL.String())
@@ -108,22 +124,47 @@ func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		r, resp := proxy.filterRequest(r, ctx)
 
 		if resp == nil {
-			removeProxyHeaders(ctx, r)
+			if isWebSocketRequest(r) {
+				ctx.Logf("Request looks like websocket upgrade.")
+				proxy.serveWebsocket(ctx, w, r)
+			}
+
+			if !proxy.KeepHeader {
+				removeProxyHeaders(ctx, r)
+			}
 			resp, err = ctx.RoundTrip(r)
 			if err != nil {
 				ctx.Error = err
 				resp = proxy.filterResponse(nil, ctx)
-				if resp == nil {
-					ctx.Logf("error read response %v %v:", r.URL.Host, err.Error())
-					http.Error(w, err.Error(), 500)
-					return
-				}
+
+			}
+			if resp != nil {
+				ctx.Logf("Received response %v", resp.Status)
 			}
-			ctx.Logf("Received response %v", resp.Status)
 		}
-		origBody := resp.Body
+
+		var origBody io.ReadCloser
+
+		if resp != nil {
+			origBody = resp.Body
+			defer origBody.Close()
+		}
+
 		resp = proxy.filterResponse(resp, ctx)
-		defer origBody.Close()
+
+		if resp == nil {
+			var errorString string
+			if ctx.Error != nil {
+				errorString = "error read response " + r.URL.Host + " : " + ctx.Error.Error()
+				ctx.Logf(errorString)
+				http.Error(w, ctx.Error.Error(), 500)
+			} else {
+				errorString = "error read response " + r.URL.Host
+				ctx.Logf(errorString)
+				http.Error(w, errorString, 500)
+			}
+			return
+		}
 		ctx.Logf("Copying response to client %v [%d]", resp.Status, resp.StatusCode)
 		// http.ResponseWriter will take care of filling the correct response length
 		// Setting it now, might impose wrong value, contradicting the actual new
@@ -134,7 +175,7 @@ func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		if origBody != resp.Body {
 			resp.Header.Del("Content-Length")
 		}
-		copyHeaders(w.Header(), resp.Header)
+		copyHeaders(w.Header(), resp.Header, proxy.KeepDestinationHeaders)
 		w.WriteHeader(resp.StatusCode)
 		nr, err := io.Copy(w, resp.Body)
 		if err := resp.Body.Close(); err != nil {
@@ -144,7 +185,7 @@ func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 }
 
-// New proxy server, logs to StdErr by default
+// NewProxyHttpServer creates and returns a proxy server, logging to stderr by default
 func NewProxyHttpServer() *ProxyHttpServer {
 	proxy := ProxyHttpServer{
 		Logger:        log.New(os.Stderr, "", log.LstdFlags),
@@ -154,9 +195,10 @@ func NewProxyHttpServer() *ProxyHttpServer {
 		NonproxyHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 			http.Error(w, "This is a proxy server. Does not respond to non-proxy requests.", 500)
 		}),
-		Tr: &http.Transport{TLSClientConfig: tlsClientSkipVerify,
-			Proxy: http.ProxyFromEnvironment},
+		Tr: &http.Transport{TLSClientConfig: tlsClientSkipVerify, Proxy: http.ProxyFromEnvironment},
 	}
+
 	proxy.ConnectDial = dialerFromEnv(&proxy)
+
 	return &proxy
 }

+ 1 - 0
vendor/github.com/elazarl/goproxy/responses.go

@@ -21,6 +21,7 @@ func NewResponse(r *http.Request, contentType string, status int, body string) *
 	resp.Header = make(http.Header)
 	resp.Header.Add("Content-Type", contentType)
 	resp.StatusCode = status
+	resp.Status = http.StatusText(status)
 	buf := bytes.NewBufferString(body)
 	resp.ContentLength = int64(buf.Len())
 	resp.Body = ioutil.NopCloser(buf)

+ 32 - 9
vendor/github.com/elazarl/goproxy/signer.go

@@ -1,12 +1,17 @@
 package goproxy
 
 import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
 	"crypto/rsa"
 	"crypto/sha1"
 	"crypto/tls"
 	"crypto/x509"
 	"crypto/x509/pkix"
+	"fmt"
 	"math/big"
+	"math/rand"
 	"net"
 	"runtime"
 	"sort"
@@ -32,7 +37,7 @@ func hashSortedBigInt(lst []string) *big.Int {
 
 var goproxySignerVersion = ":goroxy1"
 
-func signHost(ca tls.Certificate, hosts []string) (cert tls.Certificate, err error) {
+func signHost(ca tls.Certificate, hosts []string) (cert *tls.Certificate, err error) {
 	var x509ca *x509.Certificate
 
 	// Use the provided ca and not the global GoproxyCa for certificate generation.
@@ -44,9 +49,8 @@ func signHost(ca tls.Certificate, hosts []string) (cert tls.Certificate, err err
 	if err != nil {
 		panic(err)
 	}
-	hash := hashSorted(append(hosts, goproxySignerVersion, ":"+runtime.Version()))
-	serial := new(big.Int)
-	serial.SetBytes(hash)
+
+	serial := big.NewInt(rand.Int63())
 	template := x509.Certificate{
 		// TODO(elazar): instead of this ugly hack, just encode the certificate and hash the binary form.
 		SerialNumber: serial,
@@ -66,22 +70,41 @@ func signHost(ca tls.Certificate, hosts []string) (cert tls.Certificate, err err
 			template.IPAddresses = append(template.IPAddresses, ip)
 		} else {
 			template.DNSNames = append(template.DNSNames, h)
+			template.Subject.CommonName = h
 		}
 	}
+
+	hash := hashSorted(append(hosts, goproxySignerVersion, ":"+runtime.Version()))
 	var csprng CounterEncryptorRand
 	if csprng, err = NewCounterEncryptorRandFromKey(ca.PrivateKey, hash); err != nil {
 		return
 	}
-	var certpriv *rsa.PrivateKey
-	if certpriv, err = rsa.GenerateKey(&csprng, 1024); err != nil {
-		return
+
+	var certpriv crypto.Signer
+	switch ca.PrivateKey.(type) {
+	case *rsa.PrivateKey:
+		if certpriv, err = rsa.GenerateKey(&csprng, 2048); err != nil {
+			return
+		}
+	case *ecdsa.PrivateKey:
+		if certpriv, err = ecdsa.GenerateKey(elliptic.P256(), &csprng); err != nil {
+			return
+		}
+	default:
+		err = fmt.Errorf("unsupported key type %T", ca.PrivateKey)
 	}
+
 	var derBytes []byte
-	if derBytes, err = x509.CreateCertificate(&csprng, &template, x509ca, &certpriv.PublicKey, ca.PrivateKey); err != nil {
+	if derBytes, err = x509.CreateCertificate(&csprng, &template, x509ca, certpriv.Public(), ca.PrivateKey); err != nil {
 		return
 	}
-	return tls.Certificate{
+	return &tls.Certificate{
 		Certificate: [][]byte{derBytes, ca.Certificate[0]},
 		PrivateKey:  certpriv,
 	}, nil
 }
+
+func init() {
+	// Avoid deterministic random numbers
+	rand.Seed(time.Now().UnixNano())
+}

+ 121 - 0
vendor/github.com/elazarl/goproxy/websocket.go

@@ -0,0 +1,121 @@
+package goproxy
+
+import (
+	"bufio"
+	"crypto/tls"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+func headerContains(header http.Header, name string, value string) bool {
+	for _, v := range header[name] {
+		for _, s := range strings.Split(v, ",") {
+			if strings.EqualFold(value, strings.TrimSpace(s)) {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func isWebSocketRequest(r *http.Request) bool {
+	return headerContains(r.Header, "Connection", "upgrade") &&
+		headerContains(r.Header, "Upgrade", "websocket")
+}
+
+func (proxy *ProxyHttpServer) serveWebsocketTLS(ctx *ProxyCtx, w http.ResponseWriter, req *http.Request, tlsConfig *tls.Config, clientConn *tls.Conn) {
+	targetURL := url.URL{Scheme: "wss", Host: req.URL.Host, Path: req.URL.Path}
+
+	// Connect to upstream
+	targetConn, err := tls.Dial("tcp", targetURL.Host, tlsConfig)
+	if err != nil {
+		ctx.Warnf("Error dialing target site: %v", err)
+		return
+	}
+	defer targetConn.Close()
+
+	// Perform handshake
+	if err := proxy.websocketHandshake(ctx, req, targetConn, clientConn); err != nil {
+		ctx.Warnf("Websocket handshake error: %v", err)
+		return
+	}
+
+	// Proxy wss connection
+	proxy.proxyWebsocket(ctx, targetConn, clientConn)
+}
+
+func (proxy *ProxyHttpServer) serveWebsocket(ctx *ProxyCtx, w http.ResponseWriter, req *http.Request) {
+	targetURL := url.URL{Scheme: "ws", Host: req.URL.Host, Path: req.URL.Path}
+
+	targetConn, err := proxy.connectDial("tcp", targetURL.Host)
+	if err != nil {
+		ctx.Warnf("Error dialing target site: %v", err)
+		return
+	}
+	defer targetConn.Close()
+
+	// Connect to Client
+	hj, ok := w.(http.Hijacker)
+	if !ok {
+		panic("httpserver does not support hijacking")
+	}
+	clientConn, _, err := hj.Hijack()
+	if err != nil {
+		ctx.Warnf("Hijack error: %v", err)
+		return
+	}
+
+	// Perform handshake
+	if err := proxy.websocketHandshake(ctx, req, targetConn, clientConn); err != nil {
+		ctx.Warnf("Websocket handshake error: %v", err)
+		return
+	}
+
+	// Proxy ws connection
+	proxy.proxyWebsocket(ctx, targetConn, clientConn)
+}
+
+func (proxy *ProxyHttpServer) websocketHandshake(ctx *ProxyCtx, req *http.Request, targetSiteConn io.ReadWriter, clientConn io.ReadWriter) error {
+	// write handshake request to target
+	err := req.Write(targetSiteConn)
+	if err != nil {
+		ctx.Warnf("Error writing upgrade request: %v", err)
+		return err
+	}
+
+	targetTLSReader := bufio.NewReader(targetSiteConn)
+
+	// Read handshake response from target
+	resp, err := http.ReadResponse(targetTLSReader, req)
+	if err != nil {
+		ctx.Warnf("Error reading handhsake response  %v", err)
+		return err
+	}
+
+	// Run response through handlers
+	resp = proxy.filterResponse(resp, ctx)
+
+	// Proxy handshake back to client
+	err = resp.Write(clientConn)
+	if err != nil {
+		ctx.Warnf("Error writing handshake response: %v", err)
+		return err
+	}
+	return nil
+}
+
+func (proxy *ProxyHttpServer) proxyWebsocket(ctx *ProxyCtx, dest io.ReadWriter, source io.ReadWriter) {
+	errChan := make(chan error, 2)
+	cp := func(dst io.Writer, src io.Reader) {
+		_, err := io.Copy(dst, src)
+		ctx.Warnf("Websocket error: %v", err)
+		errChan <- err
+	}
+
+	// Start proxying websocket data
+	go cp(dest, source)
+	go cp(source, dest)
+	<-errChan
+}

+ 9 - 3
vendor/vendor.json

@@ -257,10 +257,16 @@
 			"revisionTime": "2018-01-09T07:02:41Z"
 		},
 		{
-			"checksumSHA1": "RbvI/I41Th10HSa2dC3IS2JzW/w=",
+			"checksumSHA1": "njovpO35R7czcCeBKwQV3Hnee0g=",
 			"path": "github.com/elazarl/goproxy",
-			"revision": "a96fa3a318260eab29abaf32f7128c9eb07fb073",
-			"revisionTime": "2017-11-01T14:35:03Z"
+			"revision": "0581fc3aee2d07555835bed1a876aca196a4a511",
+			"revisionTime": "2020-08-09T06:25:17Z"
+		},
+		{
+			"checksumSHA1": "z60viLQyKoqp8U3O046JbTaoeOs=",
+			"path": "github.com/elazarl/goproxy/ext/auth",
+			"revision": "0581fc3aee2d07555835bed1a876aca196a4a511",
+			"revisionTime": "2020-08-09T06:25:17Z"
 		},
 		{
 			"checksumSHA1": "+feaBcESiI7xYo/JHfi/tBLBd0M=",