I tried to add Digest access authentication support in http.Transport today. currently it is available for proxy servers. (compatible with basic auth,but not tested.)
I hope the official can integrate it. After all, this is a base library.
Reference: https://github.com/delphinus/go-digest-request
package http
type Transport struct {
//...
// digest auth fields
nonceCount nonceCount
authParts map[string]string
needAuth bool
basicAuth bool
}
const nonce = "nonce"
const qop = "qop"
const realm = "realm"
const proxyAuthenticate = "Proxy-Authenticate"
const proxyAuthorization = "Proxy-Authorization"
var digestAuthHeanderswanted = []string{nonce, qop, realm}
func getRandomString(l int) string {
str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
bytes := []byte(str)
var result []byte
lstr := len(str) - 1
for i := 0; i < l; i++ {
n := getRandomInt(0, lstr)
result = append(result, bytes[n])
}
return string(result)
}
var r = rand.New(rand.NewSource(time.Now().UnixNano()))
func getRandomInt(min, max int) int {
sub := max - min + 1
if sub <= 1 {
return min
}
return min + r.Intn(sub)
}
func (t *Transport) makeAuthorization(proxy *url.URL, req *Request, parts map[string]string) string {
username, password := "", ""
if u := proxy.User; u != nil {
username = u.Username()
password, _ = u.Password()
}
ha1 := getMD5([]string{username, parts[realm], password})
ha2 := getMD5([]string{req.Method, req.URL.String()})
cnonce := getRandomString(16)
nc := t.getNonceCount()
response := getMD5([]string{
ha1,
parts[nonce],
nc,
cnonce,
parts[qop],
ha2,
})
return fmt.Sprintf(
`Digest username="%s", realm="%s", nonce="%s", uri="%s", qop=%s, nc=%s, cnonce="%s", response="%s"`,
username,
parts[realm],
parts[nonce],
req.URL.String(),
parts[qop],
nc,
cnonce,
response,
)
}
func makeParts(resp *Response) (map[string]string, error) {
headers := strings.Split(resp.Header[proxyAuthenticate][0], ",")
parts := make(map[string]string, len(digestAuthHeanderswanted))
for _, r := range headers {
for _, w := range digestAuthHeanderswanted {
if strings.Contains(r, w) {
parts[w] = strings.Split(r, `"`)[1]
}
}
}
if len(parts) != len(digestAuthHeanderswanted) {
return nil, fmt.Errorf("header is invalid: %+v", parts)
}
return parts, nil
}
type nonceCount int
func (nc nonceCount) String() string {
c := int(nc)
return fmt.Sprintf("%08x", c)
}
func getMD5(texts []string) string {
h := md5.New()
_, _ = io.WriteString(h, strings.Join(texts, ":"))
return hex.EncodeToString(h.Sum(nil))
}
func (t *Transport) getNonceCount() string {
t.nonceCount++
return t.nonceCount.String()
}
func (t *Transport) roundTrip(req *Request) (*Response, error) {
//...
isHTTP := scheme == "http" || scheme == "https"
if isHTTP {
if scheme == "http" && t.needAuth && !t.basicAuth {
p, err := t.Proxy(req)
if err == nil {
auth := t.makeAuthorization(p, req, t.authParts)
req.Header.Add(proxyAuthorization, auth)
}
}
//...
}
//...
for {
//...
if err == nil {
if resp.StatusCode == 407 && req.URL.Scheme == "http" {
if strings.HasPrefix(resp.Header[proxyAuthenticate][0], "Basic") {
t.basicAuth = true
} else {
t.basicAuth = false
t.authParts, err = makeParts(resp)
if err != nil {
return nil, err
}
}
t.needAuth = true
return t.roundTrip(req)
}
return resp, nil
}
//...
}
}
func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistConn, error) {
// Proxy setup.
switch {
//...
case cm.targetScheme == "http":
pconn.isProxy = true
if t.needAuth && t.basicAuth {
if pa := cm.proxyAuth(); pa != "" {
pconn.mutateHeaderFunc = func(h Header) {
h.Set(proxyAuthorization, pa)
}
}
}
case cm.targetScheme == "https":
conn := pconn.conn
hdr := t.ProxyConnectHeader
if hdr == nil {
hdr = make(Header)
}
connectReq := &Request{
Method: "CONNECT",
URL: &url.URL{Opaque: cm.targetAddr},
Host: cm.targetAddr,
Header: hdr,
}
if t.needAuth {
auth := ""
if t.basicAuth {
if pa := cm.proxyAuth(); pa != "" {
auth = pa
}
} else {
auth = t.makeAuthorization(cm.proxyURL, connectReq, t.authParts)
}
connectReq.Header.Add(proxyAuthorization, auth)
}
connectReq.Write(conn)
br := bufio.NewReader(conn)
resp, err := ReadResponse(br, connectReq)
if err != nil {
conn.Close()
return nil, err
}
if resp.StatusCode != 200 {
if resp.StatusCode == 407 {
t.authParts, err = makeParts(resp)
if err != nil {
return nil, err
}
t.needAuth = true
return t.dialConn(ctx, cm)
}
//...
}
}
//...
}
I tried to add Digest access authentication support in http.Transport today. currently it is available for proxy servers. (compatible with basic auth,but not tested.)
I hope the official can integrate it. After all, this is a base library.
Reference: https://github.com/delphinus/go-digest-request
Mainly modified:
Transport.roundTrip
Transport.dialConn