Răsfoiți Sursa

upstreamproxy authentication in progress

Eugene Fryntov 10 ani în urmă
părinte
comite
f47a983b29

+ 33 - 0
psiphon/upstreamproxy/auth_basic.go

@@ -0,0 +1,33 @@
+package upstreamproxy
+
+import (
+	"errors"
+	"net/http"
+)
+
+type BasicHttpAuthState int
+
+const (
+	BASIC_HTTP_AUTH_STATE_NEW BasicHttpAuthState = iota
+	BASIC_HTTP_AUTH_STATE_RESPONSE_GENERATED
+)
+
+type BasicHttpAuthenticator struct {
+	state     BasicHttpAuthState
+	challenge string
+}
+
+func newBasicAuthenticator(challenge string) *BasicHttpAuthenticator {
+	return &BasicHttpAuthenticator{state: BASIC_HTTP_AUTH_STATE_NEW,
+		challenge: challenge}
+}
+
+func (b BasicHttpAuthenticator) authenticate(req *http.Request, username, password string) error {
+	if b.state == BASIC_HTTP_AUTH_STATE_NEW {
+		b.state = BASIC_HTTP_AUTH_STATE_RESPONSE_GENERATED
+		req.SetBasicAuth(username, password)
+		return nil
+	} else {
+		return errors.New("Authentication is not accepted by the proxy server")
+	}
+}

+ 27 - 0
psiphon/upstreamproxy/auth_digest.go

@@ -0,0 +1,27 @@
+package upstreamproxy
+
+import (
+	"errors"
+	"net/http"
+)
+
+type DigestHttpAuthState int
+
+const (
+	DIGEST_HTTP_AUTH_STATE_NEW DigestHttpAuthState = iota
+	DIGEST_HTTP_AUTH_STATE_RESPONSE_GENERATED
+)
+
+type DigestHttpAuthenticator struct {
+	state     DigestHttpAuthState
+	challenge string
+}
+
+func newDigestAuthenticator(challenge string) *DigestHttpAuthenticator {
+	return &DigestHttpAuthenticator{state: DIGEST_HTTP_AUTH_STATE_NEW,
+		challenge: challenge}
+}
+
+func (b DigestHttpAuthenticator) authenticate(req *http.Request, username, password string) error {
+	return errors.New("Digest auth is not implemented")
+}

+ 28 - 0
psiphon/upstreamproxy/auth_ntlm.go

@@ -0,0 +1,28 @@
+package upstreamproxy
+
+import (
+	"errors"
+	"net/http"
+)
+
+type NTLMHttpAuthState int
+
+const (
+	NTLM_HTTP_AUTH_STATE_NEW NTLMHttpAuthState = iota
+	NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE1_GENERATED
+	NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE3_GENERATED
+)
+
+type NTLMHttpAuthenticator struct {
+	state     NTLMHttpAuthState
+	challenge string
+}
+
+func newNTLMAuthenticator(challenge string) *NTLMHttpAuthenticator {
+	return &NTLMHttpAuthenticator{state: NTLM_HTTP_AUTH_STATE_NEW,
+		challenge: challenge}
+}
+
+func (b NTLMHttpAuthenticator) authenticate(req *http.Request, username, password string) error {
+	return errors.New("NTLM auth is not implemented")
+}

+ 54 - 0
psiphon/upstreamproxy/http_authenticator.go

@@ -0,0 +1,54 @@
+package upstreamproxy
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+)
+
+type HttpAuthState int
+
+const (
+	HTTP_AUTH_STATE_UNCHALLENGED HttpAuthState = iota
+	HTTP_AUTH_STATE_CHALLENGED
+	HTTP_AUTH_STATE_FAILURE
+	HTTP_AUTH_STATE_SUCCESS
+)
+
+type HttpAuthenticator interface {
+	authenticate(req *http.Request, username, pasword string) error
+}
+
+func newHttpAuthenticator(resp *http.Response) (HttpAuthenticator, error) {
+
+	challenges := make(map[string]string)
+	headers := resp.Header[http.CanonicalHeaderKey("proxy-authenticate")]
+
+	for _, val := range headers {
+		s := strings.SplitN(val, " ", 2)
+		if len(s) == 2 {
+			challenges[s[0]] = s[1]
+		}
+		if len(s) == 1 && s[0] != "" {
+			challenges[s[0]] = ""
+		}
+	}
+        if len(challenges) == 0 {
+            return nil, fmt.Errorf("No valid challenges in the Proxy-Authenticate header")
+        }
+	// NTLM > Digest > Basic
+	if challenge, ok := challenges["NTLM"]; ok {
+		return newNTLMAuthenticator(challenge), nil
+	} else if challenge, ok := challenges["Digest"]; ok {
+		return newDigestAuthenticator(challenge), nil
+	} else if challenge, ok := challenges["Basic"]; ok {
+		return newBasicAuthenticator(challenge), nil
+	}
+
+	//Unsupported scheme
+	schemes := make([]string, 0, len(challenges))
+	for scheme := range challenges {
+		schemes = append(schemes, scheme)
+	}
+	return nil, fmt.Errorf("Unsupported proxy authentication scheme in %v", schemes)
+}

+ 129 - 58
psiphon/upstreamproxy/proxy_http.go

@@ -47,130 +47,201 @@ package upstreamproxy
 
 import (
 	"bufio"
-	"fmt"
+	"errors"
+	//"fmt"
+	"golang.org/x/net/proxy"
 	"net"
 	"net/http"
 	"net/http/httputil"
 	"net/url"
 	"time"
+)
 
-	"golang.org/x/net/proxy"
+const (
+	PROXY_SUCCESS int = iota
+	PROXY_FAILURE
+	PROXY_HANDSHAKE_IN_PROGRESS
 )
 
 // httpProxy is a HTTP connect proxy.
 type httpProxy struct {
 	hostPort string
-	haveAuth bool
 	username string
 	password string
 	forward  proxy.Dialer
 }
 
 func newHTTP(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
-	s := new(httpProxy)
-	s.hostPort = uri.Host
-	s.forward = forward
+	hp := new(httpProxy)
+	hp.hostPort = uri.Host
+	hp.forward = forward
 	if uri.User != nil {
-		s.haveAuth = true
-		s.username = uri.User.Username()
-		s.password, _ = uri.User.Password()
+		hp.username = uri.User.Username()
+		hp.password, _ = uri.User.Password()
 	}
 
-	return s, nil
+	return hp, nil
 }
 
-func (s *httpProxy) Dial(network, addr string) (net.Conn, error) {
+func (hp *httpProxy) Dial(network, addr string) (net.Conn, error) {
 	// Dial and create the http client connection.
-	c, err := s.forward.Dial("tcp", s.hostPort)
+
+	pc := &proxyConn{authState: HTTP_AUTH_STATE_UNCHALLENGED}
+	err := pc.makeNewClientConn(hp.forward, hp.hostPort)
 	if err != nil {
 		return nil, err
 	}
-	conn := new(httpConn)
-	conn.httpConn = httputil.NewClientConn(c, nil)
-	conn.remoteAddr, err = net.ResolveTCPAddr(network, addr)
-	if err != nil {
-		conn.httpConn.Close()
-		return nil, err
+
+	for {
+		err := pc.handshake(addr, hp.username, hp.password)
+		switch pc.authState {
+		case HTTP_AUTH_STATE_SUCCESS:
+			pc.hijackedConn, pc.staleReader = pc.httpClientConn.Hijack()
+			return pc, nil
+		case HTTP_AUTH_STATE_FAILURE:
+			return nil, err
+		case HTTP_AUTH_STATE_CHALLENGED:
+			// the server may send Connection: close,
+			// at this point we just going to create a new
+			// ClientConn and continue the handshake
+			if err == httputil.ErrPersistEOF {
+				err := pc.makeNewClientConn(hp.forward, hp.hostPort)
+				if err != nil {
+					return nil, err
+				}
+			}
+			continue
+		}
+		panic("Illegal proxy handshake auth state")
 	}
 
+}
+
+type proxyConn struct {
+	httpClientConn *httputil.ClientConn
+	hijackedConn   net.Conn
+	staleReader    *bufio.Reader
+	authenticator  HttpAuthenticator
+	authState      HttpAuthState
+}
+
+func (pc *proxyConn) handshake(addr, username, password string) error {
 	// HACK HACK HACK HACK.  http.ReadRequest also does this.
 	reqURL, err := url.Parse("http://" + addr)
 	if err != nil {
-		conn.httpConn.Close()
-		return nil, err
+		pc.httpClientConn.Close()
+		pc.authState = HTTP_AUTH_STATE_FAILURE
+		return err
 	}
 	reqURL.Scheme = ""
 
 	req, err := http.NewRequest("CONNECT", reqURL.String(), nil)
 	if err != nil {
-		conn.httpConn.Close()
-		return nil, err
+		pc.httpClientConn.Close()
+		pc.authState = HTTP_AUTH_STATE_FAILURE
+		return err
 	}
 	req.Close = false
-	if s.haveAuth {
-		req.SetBasicAuth(s.username, s.password)
-	}
 	req.Header.Set("User-Agent", "")
 
-	resp, err := conn.httpConn.Do(req)
+	if pc.authState == HTTP_AUTH_STATE_CHALLENGED {
+		if pc.authenticator == nil {
+			panic("HttpAuthenticator is not initialized, can't response to the proxy server auth challenge!")
+		}
+		err := pc.authenticator.authenticate(req, username, password)
+		if err != nil {
+			pc.authState = HTTP_AUTH_STATE_FAILURE
+			return err
+		}
+	}
+
+	resp, err := pc.httpClientConn.Do(req)
+
 	if err != nil && err != httputil.ErrPersistEOF {
-		conn.httpConn.Close()
-		return nil, err
+		pc.httpClientConn.Close()
+		pc.authState = HTTP_AUTH_STATE_FAILURE
+		return err
 	}
-	if resp.StatusCode != 200 {
-		conn.httpConn.Close()
-		return nil, fmt.Errorf("proxy error: %s", resp.Status)
+
+	if resp.StatusCode == 200 {
+		pc.authState = HTTP_AUTH_STATE_SUCCESS
+		return nil
 	}
 
-	conn.hijackedConn, conn.staleReader = conn.httpConn.Hijack()
-	return conn, nil
+	if resp.StatusCode == 407 {
+		if username == "" {
+			pc.httpClientConn.Close()
+			pc.authState = HTTP_AUTH_STATE_FAILURE
+			return errors.New("No credentials provided for proxy auth")
+		}
+
+		if pc.authState == HTTP_AUTH_STATE_UNCHALLENGED {
+			pc.authenticator, err = newHttpAuthenticator(resp)
+			if err != nil {
+				pc.httpClientConn.Close()
+				pc.authState = HTTP_AUTH_STATE_FAILURE
+				return err
+			}
+			pc.authState = HTTP_AUTH_STATE_CHALLENGED
+			return nil
+		}
+	}
+	pc.authState = HTTP_AUTH_STATE_FAILURE
+	return err
 }
 
-type httpConn struct {
-	remoteAddr   *net.TCPAddr
-	httpConn     *httputil.ClientConn
-	hijackedConn net.Conn
-	staleReader  *bufio.Reader
+func (pc *proxyConn) makeNewClientConn(dialer proxy.Dialer, addr string) error {
+	c, err := dialer.Dial("tcp", addr)
+	if err != nil {
+		pc.httpClientConn.Close()
+		return err
+	}
+	if pc.httpClientConn != nil {
+		pc.httpClientConn.Close()
+	}
+	pc.httpClientConn = httputil.NewClientConn(c, nil)
+	return nil
 }
 
-func (c *httpConn) Read(b []byte) (int, error) {
-	if c.staleReader != nil {
-		if c.staleReader.Buffered() > 0 {
-			return c.staleReader.Read(b)
+func (pc *proxyConn) Read(b []byte) (int, error) {
+	if pc.staleReader != nil {
+		if pc.staleReader.Buffered() > 0 {
+			return pc.staleReader.Read(b)
 		}
-		c.staleReader = nil
+		pc.staleReader = nil
 	}
-	return c.hijackedConn.Read(b)
+	return pc.hijackedConn.Read(b)
 }
 
-func (c *httpConn) Write(b []byte) (int, error) {
-	return c.hijackedConn.Write(b)
+func (pc *proxyConn) Write(b []byte) (int, error) {
+	return pc.hijackedConn.Write(b)
 }
 
-func (c *httpConn) Close() error {
-	return c.hijackedConn.Close()
+func (pc *proxyConn) Close() error {
+	return pc.hijackedConn.Close()
 }
 
-func (c *httpConn) LocalAddr() net.Addr {
-	return c.hijackedConn.LocalAddr()
+func (pc *proxyConn) LocalAddr() net.Addr {
+	return nil
 }
 
-func (c *httpConn) RemoteAddr() net.Addr {
-	return c.remoteAddr
+func (pc *proxyConn) RemoteAddr() net.Addr {
+	return nil
 }
 
-func (c *httpConn) SetDeadline(t time.Time) error {
-	return c.hijackedConn.SetDeadline(t)
+func (pc *proxyConn) SetDeadline(t time.Time) error {
+	return errors.New("not supported")
 }
 
-func (c *httpConn) SetReadDeadline(t time.Time) error {
-	return c.hijackedConn.SetReadDeadline(t)
+func (pc *proxyConn) SetReadDeadline(t time.Time) error {
+	return errors.New("not supported")
 }
 
-func (c *httpConn) SetWriteDeadline(t time.Time) error {
-	return c.hijackedConn.SetWriteDeadline(t)
+func (pc *proxyConn) SetWriteDeadline(t time.Time) error {
+	return errors.New("not supported")
 }
 
 func init() {
 	proxy.RegisterDialerType("http", newHTTP)
+	proxy.RegisterDialerType("https", newHTTP)
 }

+ 33 - 0
psiphon/upstreamproxy/transport_proxy_auth.go

@@ -0,0 +1,33 @@
+package upstreamproxy
+
+/*
+
+import (
+	"net/http"
+)
+type Transport struct {
+	Username  string
+	Password  string
+	Transport http.RoundTripper
+}
+
+func NewHttpTransport(username, password string, dialFn DialFunc) *Transport {
+	t := &Transport{
+		Username: username,
+		Password: password,
+	}
+	t.Transport = &http.Transport{Dial: dialFn}
+	return t
+}
+
+func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
+
+	resp, err := t.Transport.RoundTrip(req)
+	if resp.StatusCode == http.StatusProxyAuthRequired {
+		//read auth header
+		//detect auth type
+		//authenticate and call self
+	}
+	return resp, err
+}
+*/