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

Digest authentication added to upstreamproxy package

Eugene Fryntov 10 лет назад
Родитель
Сommit
c056fa5b5e

+ 6 - 6
psiphon/upstreamproxy/auth_basic.go

@@ -8,7 +8,7 @@ import (
 type BasicHttpAuthState int
 
 const (
-	BASIC_HTTP_AUTH_STATE_NEW BasicHttpAuthState = iota
+	BASIC_HTTP_AUTH_STATE_CHALLENGE_RECEIVED BasicHttpAuthState = iota
 	BASIC_HTTP_AUTH_STATE_RESPONSE_GENERATED
 )
 
@@ -18,16 +18,16 @@ type BasicHttpAuthenticator struct {
 }
 
 func newBasicAuthenticator(challenge string) *BasicHttpAuthenticator {
-	return &BasicHttpAuthenticator{state: BASIC_HTTP_AUTH_STATE_NEW,
+	return &BasicHttpAuthenticator{state: BASIC_HTTP_AUTH_STATE_CHALLENGE_RECEIVED,
 		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
+func (a BasicHttpAuthenticator) authenticate(req *http.Request, username, password string) error {
+	if a.state == BASIC_HTTP_AUTH_STATE_CHALLENGE_RECEIVED {
 		req.SetBasicAuth(username, password)
+		a.state = BASIC_HTTP_AUTH_STATE_RESPONSE_GENERATED
 		return nil
 	} else {
-		return errors.New("Authentication is not accepted by the proxy server")
+		return errors.New("Authorization is not accepted by the proxy server")
 	}
 }

+ 126 - 5
psiphon/upstreamproxy/auth_digest.go

@@ -1,14 +1,98 @@
 package upstreamproxy
 
 import (
-	"errors"
+	"crypto/md5"
+	"crypto/rand"
+	"encoding/base64"
+	//"errors"
+	"fmt"
+	"io"
 	"net/http"
+	"strings"
 )
 
+/* Adapted from https://github.com/ryanjdew/http-digest-auth-client */
+
+type DigestHeaders struct {
+	Realm     string
+	Qop       string
+	Method    string
+	Nonce     string
+	Opaque    string
+	Algorithm string
+	HA1       string
+	HA2       string
+	Cnonce    string
+	Path      string
+	Nc        int16
+	Username  string
+	Password  string
+}
+
+// ApplyAuth adds proper auth header to the passed request
+func (d *DigestHeaders) ApplyAuth(req *http.Request) {
+	d.Nc += 0x1
+	d.Cnonce = randomKey()
+	d.Method = req.Method
+	d.Path = req.URL.RequestURI()
+	d.digestChecksum()
+	response := h(strings.Join([]string{d.HA1, d.Nonce, fmt.Sprintf("%08x", d.Nc),
+		d.Cnonce, d.Qop, d.HA2}, ":"))
+	AuthHeader := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=%08x, qop=%s, response="%s", algorithm=%s`,
+		d.Username, d.Realm, d.Nonce, d.Path, d.Cnonce, d.Nc, d.Qop, response, d.Algorithm)
+	if d.Opaque != "" {
+		AuthHeader = fmt.Sprintf(`%s, opaque="%s"`, AuthHeader, d.Opaque)
+	}
+	req.Header.Set("Proxy-Authorization", AuthHeader)
+}
+
+func (d *DigestHeaders) digestChecksum() {
+	switch d.Algorithm {
+	case "MD5":
+		// A1
+		h := md5.New()
+		A1 := fmt.Sprintf("%s:%s:%s", d.Username, d.Realm, d.Password)
+		io.WriteString(h, A1)
+		d.HA1 = fmt.Sprintf("%x", h.Sum(nil))
+
+		// A2
+		h = md5.New()
+		A2 := fmt.Sprintf("%s:%s", d.Method, d.Path)
+		io.WriteString(h, A2)
+		d.HA2 = fmt.Sprintf("%x", h.Sum(nil))
+	case "MD5-sess":
+	default:
+		//token
+	}
+}
+
+func randomKey() string {
+	k := make([]byte, 12)
+	for bytes := 0; bytes < len(k); {
+		n, err := rand.Read(k[bytes:])
+		if err != nil {
+			panic("rand.Read() failed")
+		}
+		bytes += n
+	}
+	return base64.StdEncoding.EncodeToString(k)
+}
+
+/*
+H function for MD5 algorithm (returns a lower-case hex MD5 digest)
+*/
+func h(data string) string {
+	digest := md5.New()
+	digest.Write([]byte(data))
+	return fmt.Sprintf("%x", digest.Sum(nil))
+}
+
+/* End of https://github.com/ryanjdew/http-digest-auth-client code adaptation */
+
 type DigestHttpAuthState int
 
 const (
-	DIGEST_HTTP_AUTH_STATE_NEW DigestHttpAuthState = iota
+	DIGEST_HTTP_AUTH_STATE_CHALLENGE_RECEIVED DigestHttpAuthState = iota
 	DIGEST_HTTP_AUTH_STATE_RESPONSE_GENERATED
 )
 
@@ -18,10 +102,47 @@ type DigestHttpAuthenticator struct {
 }
 
 func newDigestAuthenticator(challenge string) *DigestHttpAuthenticator {
-	return &DigestHttpAuthenticator{state: DIGEST_HTTP_AUTH_STATE_NEW,
+	return &DigestHttpAuthenticator{state: DIGEST_HTTP_AUTH_STATE_CHALLENGE_RECEIVED,
 		challenge: challenge}
 }
 
-func (b DigestHttpAuthenticator) authenticate(req *http.Request, username, password string) error {
-	return errors.New("Digest auth is not implemented")
+func (a DigestHttpAuthenticator) authenticate(req *http.Request, username, password string) error {
+	if a.state == DIGEST_HTTP_AUTH_STATE_CHALLENGE_RECEIVED {
+		if len(challenge) == 0 {
+			return errors.New("Digest authentication challenge is empty")
+		}
+		//parse challenge
+		digestParams := map[string]string{}
+		for _, keyval := range strings.Split(a.challenge, ",") {
+			param := strings.SplitN(keyval, "=", 2)
+			if len(param) != 2 {
+				continue
+			}
+			digestParams[strings.Trim(param[0], "\" ")] = strings.Trim(param[1], "\" ")
+		}
+		if len(digestParams) == 0 {
+			return errors.New("Digest authentication challenge is malformed")
+		}
+
+		algorithm := digestParams["algorithm"]
+
+		d := &DigestHeaders{}
+		d.Realm = digestParams["realm"]
+		d.Qop = digestParams["qop"]
+		d.Nonce = digestParams["nonce"]
+		d.Opaque = digestParams["opaque"]
+		if algorithm == "" {
+			d.Algorithm = "MD5"
+		} else {
+			d.Algorithm = digestParams["algorithm"]
+		}
+		d.Nc = 0x0
+		d.Username = username
+		d.Password = password
+		d.ApplyAuth(req)
+		a.state = DIGEST_HTTP_AUTH_STATE_RESPONSE_GENERATED
+		return nil
+
+		return errors.New("Authorization is not accepted by the proxy server")
+	}
 }

+ 3 - 3
psiphon/upstreamproxy/auth_ntlm.go

@@ -8,7 +8,7 @@ import (
 type NTLMHttpAuthState int
 
 const (
-	NTLM_HTTP_AUTH_STATE_NEW NTLMHttpAuthState = iota
+	NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED NTLMHttpAuthState = iota
 	NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE1_GENERATED
 	NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE3_GENERATED
 )
@@ -19,10 +19,10 @@ type NTLMHttpAuthenticator struct {
 }
 
 func newNTLMAuthenticator(challenge string) *NTLMHttpAuthenticator {
-	return &NTLMHttpAuthenticator{state: NTLM_HTTP_AUTH_STATE_NEW,
+	return &NTLMHttpAuthenticator{state: NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED,
 		challenge: challenge}
 }
 
-func (b NTLMHttpAuthenticator) authenticate(req *http.Request, username, password string) error {
+func (a NTLMHttpAuthenticator) authenticate(req *http.Request, username, password string) error {
 	return errors.New("NTLM auth is not implemented")
 }

+ 3 - 3
psiphon/upstreamproxy/http_authenticator.go

@@ -33,9 +33,9 @@ func newHttpAuthenticator(resp *http.Response) (HttpAuthenticator, error) {
 			challenges[s[0]] = ""
 		}
 	}
-        if len(challenges) == 0 {
-            return nil, fmt.Errorf("No valid challenges in the Proxy-Authenticate header")
-        }
+	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