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

Implemented MD5-sess for Digest authentication, 'uri' fix in authorization response

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

+ 24 - 17
psiphon/upstreamproxy/auth_digest.go

@@ -6,7 +6,6 @@ import (
 	"encoding/base64"
 	"errors"
 	"fmt"
-	"io"
 	"net/http"
 	"strings"
 )
@@ -23,7 +22,7 @@ type DigestHeaders struct {
 	HA1       string
 	HA2       string
 	Cnonce    string
-	Path      string
+	Uri       string
 	Nc        int16
 	Username  string
 	Password  string
@@ -34,12 +33,11 @@ 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)
+	AuthHeader := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s", qop=%s, nc=%08x, cnonce="%s", algorithm=%s`,
+		d.Username, d.Realm, d.Nonce, d.Uri, response, d.Qop, d.Nc, d.Cnonce, d.Algorithm)
 	if d.Opaque != "" {
 		AuthHeader = fmt.Sprintf(`%s, opaque="%s"`, AuthHeader, d.Opaque)
 	}
@@ -47,23 +45,28 @@ func (d *DigestHeaders) ApplyAuth(req *http.Request) {
 }
 
 func (d *DigestHeaders) digestChecksum() {
+	var A1 string
 	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))
+		//HA1=MD5(username:realm:password)
+		A1 = fmt.Sprintf("%s:%s:%s", d.Username, d.Realm, d.Password)
 
-		// 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":
+		// HA1=MD5(MD5(username:realm:password):nonce:cnonce)
+		str := fmt.Sprintf("%s:%s:%s", d.Username, d.Realm, d.Password)
+		A1 = fmt.Sprintf("%s:%s:%s", h(str), d.Nonce, d.Cnonce)
 	default:
 		//token
 	}
+	if A1 == "" {
+		return
+	}
+	//HA1
+	d.HA1 = h(A1)
+	// HA2
+	A2 := fmt.Sprintf("%s:%s", d.Method, d.Uri)
+	d.HA2 = h(A2)
+
 }
 
 func randomKey() string {
@@ -73,6 +76,7 @@ func randomKey() string {
 		if err != nil {
 			panic("rand.Read() failed")
 		}
+		k[bytes] = byte(bytes)
 		bytes += n
 	}
 	return base64.StdEncoding.EncodeToString(k)
@@ -87,8 +91,6 @@ func h(data string) string {
 	return fmt.Sprintf("%x", digest.Sum(nil))
 }
 
-/* End of https://github.com/ryanjdew/http-digest-auth-client code adaptation */
-
 func digestAuthenticate(req *http.Request, challenge, username, password string) error {
 	if len(challenge) == 0 {
 		return errors.New("Digest authentication challenge is empty")
@@ -109,6 +111,11 @@ func digestAuthenticate(req *http.Request, challenge, username, password string)
 	algorithm := digestParams["algorithm"]
 
 	d := &DigestHeaders{}
+	if req.Method == "CONNECT" {
+		d.Uri = req.URL.Host
+	} else {
+		d.Uri = req.URL.Scheme + "://" + req.URL.Host + req.URL.RequestURI()
+	}
 	d.Realm = digestParams["realm"]
 	d.Qop = digestParams["qop"]
 	d.Nonce = digestParams["nonce"]

+ 0 - 281
psiphon/upstreamproxy/ntlm/ntlm.go

@@ -1,281 +0,0 @@
-/*
-This project is licensed under the MIT license as stated below:
-
-Copyright (C) 2013 James McKaskill
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-*/
-package ntlm
-
-import (
-	"code.google.com/p/go.crypto/md4"
-	"crypto/des"
-	"encoding/binary"
-	"errors"
-	"strings"
-	"unicode/utf16"
-)
-
-func append16(v []byte, val uint16) []byte {
-	return append(v, byte(val), byte(val>>8))
-}
-
-func append32(v []byte, val uint16) []byte {
-	return append(v, byte(val), byte(val>>8), byte(val>>16), byte(val>>24))
-}
-
-func consume16(v []byte) (uint16, []byte) {
-	if len(v) < 2 {
-		panic(ErrProtocol)
-	}
-	return uint16(v[0]) | uint16(v[1])<<8, v[2:]
-}
-
-func consume32(v []byte) (uint32, []byte) {
-	if len(v) < 4 {
-		panic(ErrProtocol)
-	}
-	return uint32(v[0]) | uint32(v[1])<<8 | uint32(v[2])<<16 | uint32(v[3])<<24, v[4:]
-}
-
-func consume(v []byte, n int) ([]byte, []byte) {
-	if n < 0 || len(v) < n {
-		panic(ErrProtocol)
-	}
-	return v[:n], v[n:]
-}
-
-var put32 = binary.LittleEndian.PutUint32
-var put16 = binary.LittleEndian.PutUint16
-
-const (
-	negotiateUnicode    = 0x0001 // Text strings are in unicode
-	negotiateOEM        = 0x0002 // Text strings are in OEM
-	requestTarget       = 0x0004 // Server return its auth realm
-	negotiateSign       = 0x0010 // Request signature capability
-	negotiateSeal       = 0x0020 // Request confidentiality
-	negotiateLMKey      = 0x0080 // Generate session key
-	negotiateNTLM       = 0x0200 // NTLM authentication
-	negotiateLocalCall  = 0x4000 // client/server on same machine
-	negotiateAlwaysSign = 0x8000 // Sign for all security levels
-)
-
-var (
-	ErrProtocol = errors.New("ntlm: protocol error")
-)
-
-func Negotiate() []byte {
-	var ret []byte
-	flags := negotiateAlwaysSign | negotiateNTLM | requestTarget | negotiateOEM
-
-	ret = append(ret, "NTLMSSP\x00"...) // protocol
-	ret = append32(ret, 1)              // type
-	ret = append32(ret, uint16(flags))  // flags
-	ret = append16(ret, 0)              // NT domain name length
-	ret = append16(ret, 0)              // NT domain name max length
-	ret = append32(ret, 0)              // NT domain name offset
-	ret = append16(ret, 0)              // local workstation name length
-	ret = append16(ret, 0)              // local workstation name max length
-	ret = append32(ret, 0)              // local workstation name offset
-	ret = append16(ret, 0)              // unknown name length
-	ret = append16(ret, 0)              // ...
-	ret = append16(ret, 0x30)           // unknown offset
-	ret = append16(ret, 0)              // unknown name length
-	ret = append16(ret, 0)              // ...
-	ret = append16(ret, 0x30)           // unknown offset
-
-	return ret
-}
-
-func fromUTF16LE(d []byte) string {
-	u16 := make([]uint16, len(d)/2)
-	for i := 0; i < len(d); i += 2 {
-		u16 = append(u16, uint16(d[0])|uint16(d[1])<<8)
-	}
-
-	return string(utf16.Decode(u16))
-}
-
-func appendUTF16LE(v []byte, val string) []byte {
-	for _, r := range val {
-		if utf16.IsSurrogate(r) {
-			r1, r2 := utf16.EncodeRune(r)
-			v = append16(v, uint16(r1))
-			v = append16(v, uint16(r2))
-		} else {
-			v = append16(v, uint16(r))
-		}
-	}
-	return v
-}
-
-func des56To64(dst, src []byte) {
-	dst[0] = src[0]
-	dst[1] = (src[1] >> 1) | (src[0] << 7)
-	dst[2] = (src[2] >> 2) | (src[1] << 6)
-	dst[3] = (src[3] >> 3) | (src[2] << 5)
-	dst[4] = (src[4] >> 4) | (src[3] << 4)
-	dst[5] = (src[5] >> 5) | (src[4] << 3)
-	dst[6] = (src[6] >> 6) | (src[5] << 2)
-	dst[7] = src[6] << 1
-
-	// fix parity
-	for i := 0; i < 8; i++ {
-		c := 0
-		for bit := uint(0); bit < 8; bit++ {
-			if (dst[i] & (1 << bit)) != 0 {
-				c++
-			}
-		}
-		if (c & 1) == 0 {
-			dst[i] ^= 1
-		}
-	}
-}
-
-func calcNTLMResponse(nonce [8]byte, hash [21]byte) [24]byte {
-	var ret [24]byte
-	var key [24]byte
-
-	des56To64(key[:8], hash[:7])
-	des56To64(key[8:16], hash[7:14])
-	des56To64(key[16:], hash[14:])
-
-	blk, _ := des.NewCipher(key[:8])
-	blk.Encrypt(ret[:8], nonce[:])
-
-	blk, _ = des.NewCipher(key[8:16])
-	blk.Encrypt(ret[8:16], nonce[:])
-
-	blk, _ = des.NewCipher(key[16:])
-	blk.Encrypt(ret[16:], nonce[:])
-
-	return ret
-}
-
-func calcLanManResponse(nonce [8]byte, password string) [24]byte {
-	var lmpass [14]byte
-	var key [16]byte
-	var hash [21]byte
-
-	copy(lmpass[:14], []byte(strings.ToUpper(password)))
-
-	des56To64(key[:8], lmpass[:7])
-	des56To64(key[8:], lmpass[7:])
-
-	blk, _ := des.NewCipher(key[:8])
-	blk.Encrypt(hash[:8], []byte("KGS!@#$%"))
-
-	blk, _ = des.NewCipher(key[8:])
-	blk.Encrypt(hash[8:], []byte("KGS!@#$%"))
-
-	return calcNTLMResponse(nonce, hash)
-}
-
-func calcNTResponse(nonce [8]byte, password string) [24]byte {
-	var hash [21]byte
-	h := md4.New()
-	h.Write(appendUTF16LE(nil, password))
-	h.Sum(hash[:0])
-	return calcNTLMResponse(nonce, hash)
-}
-
-const (
-	dataWINSName    = 1
-	dataNTDomain    = 2
-	dataDNSName     = 3
-	dataWin2KDomain = 4
-)
-
-func Authenticate(chlg []byte, domain, user, password string) (v []byte, err error) {
-	defer func() {
-		if v := recover(); v != nil {
-			err, _ = v.(error)
-		}
-	}()
-
-	proto, chlg := consume(chlg, len("NTLMSSP\x00"))
-	if string(proto) != "NTLMSSP\x00" {
-		return nil, ErrProtocol
-	}
-
-	domain16 := appendUTF16LE(nil, domain)
-	user16 := appendUTF16LE(nil, user)
-
-	typ, chlg := consume32(chlg)       // Type 2
-	domainLen, chlg := consume16(chlg) // NT domain name length
-	_, chlg = consume16(chlg)          // NT domain name max length
-	_, chlg = consume32(chlg)          // NT domain name offset
-	_, chlg = consume32(chlg)          // flags
-	nonce, chlg := consume(chlg, 8)    // nonce
-	_, chlg = consume(chlg, 8)         // zero
-	dataLen, chlg := consume16(chlg)   // length of data following domain
-	_, chlg = consume16(chlg)          // max length of data following domain
-	_, chlg = consume32(chlg)          // offset of data following domain
-
-	_, chlg = consume(chlg, int(domainLen)) // server domain
-	alldata, chlg := consume(chlg, int(dataLen))
-
-	if typ != 2 {
-		return nil, ErrProtocol
-	}
-
-	for len(alldata) > 0 {
-		_, alldata := consume16(alldata) // type of this data item
-		length, alldata := consume16(alldata)
-		_, alldata = consume(alldata, int(length))
-	}
-
-	var noncev [8]byte
-	copy(noncev[:], nonce)
-
-	lanman := calcLanManResponse(noncev, password)
-	nt := calcNTResponse(noncev, password)
-
-	auth := make([]byte, 48)
-	copy(auth, []byte("NTLMSSP\x00"))
-	put32(auth[8:], 3) // type
-
-	put16(auth[12:], uint16(len(lanman)))                              // LanManager response length
-	put16(auth[14:], uint16(len(lanman)))                              // LanManager response max length
-	put32(auth[16:], uint32(48+len(domain16)+len(user16)))             // LanManager response offset
-	put16(auth[20:], uint16(len(nt)))                                  // NT response length
-	put16(auth[22:], uint16(len(nt)))                                  // NT response max length
-	put32(auth[24:], uint32(48+len(domain16)+len(user16)+len(lanman))) // NT repsonse offset
-	put16(auth[28:], uint16(len(domain16)))                            // username NT domain length
-	put16(auth[30:], uint16(len(domain16)))                            // username NT domain max length
-	put32(auth[32:], 48)                                               // username NT domain offset
-	put16(auth[36:], uint16(len(user16)))                              // username length
-	put16(auth[38:], uint16(len(user16)))                              // username max length
-	put32(auth[40:], uint32(48+len(domain16)))                         // username offset
-	put16(auth[44:], 0)                                                // local workstation name length
-	put16(auth[46:], 0)                                                // local workstation name max length
-	put32(auth[48:], 0)                                                // local workstation name offset
-	put16(auth[52:], 0)                                                // session key length
-	put16(auth[54:], 0)                                                // session key max length
-	put32(auth[56:], 0)                                                // session key offset
-	put32(auth[60:], 0x8201)                                           // flags
-
-	auth = append(auth, domain16...)
-	auth = append(auth, user16...)
-	auth = append(auth, lanman[:]...)
-	auth = append(auth, nt[:]...)
-
-	return auth, nil
-}

+ 12 - 8
psiphon/upstreamproxy/proxy_http.go

@@ -50,6 +50,7 @@ import (
 	"errors"
 	"fmt"
 	"golang.org/x/net/proxy"
+	//"io/ioutil"
 	"net"
 	"net/http"
 	"net/http/httputil"
@@ -126,9 +127,10 @@ func (hp *httpProxy) Dial(network, addr string) (net.Conn, error) {
 	pc := &proxyConn{authState: HTTP_AUTH_STATE_UNCHALLENGED}
 	err := pc.makeNewClientConn(hp.forward, hp.hostPort)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("makeNewClientConn error: %v", err)
 	}
 
+	//TODO: count handshake attempts
 	for {
 		err := pc.handshake(addr, hp.username, hp.password)
 		switch pc.authState {
@@ -142,15 +144,17 @@ func (hp *httpProxy) Dial(network, addr string) (net.Conn, error) {
 			// 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)
+				err = pc.makeNewClientConn(hp.forward, hp.hostPort)
 				if err != nil {
-					return nil, err
+					return nil, fmt.Errorf("makeNewClientConn error: %v", err)
 				}
 			}
 			continue
+		default:
+			panic("Illegal proxy handshake auth state")
 		}
-		panic("Illegal proxy handshake auth state")
 	}
+	return nil, fmt.Errorf("Unknown handshake error")
 
 }
 
@@ -210,6 +214,7 @@ func (pc *proxyConn) handshake(addr, username, password string) error {
 			pc.authState = HTTP_AUTH_STATE_FAILURE
 			return errors.New("No credentials provided for proxy auth")
 		}
+		return err
 	}
 	pc.authState = HTTP_AUTH_STATE_FAILURE
 	return err
@@ -217,13 +222,12 @@ func (pc *proxyConn) handshake(addr, username, password string) error {
 
 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()
 	}
+	if err != nil {
+		return err
+	}
 	pc.httpClientConn = httputil.NewClientConn(c, nil)
 	return nil
 }