package auth import ( "crypto/md5" "crypto/rand" "encoding/base64" "fmt" "io" "net/http" "net/url" "strings" ) type myjar struct { jar map[string][]*http.Cookie } func (p *myjar) SetCookies(u *url.URL, cookies []*http.Cookie) { p.jar[u.Host] = cookies } func (p *myjar) Cookies(u *url.URL) []*http.Cookie { return p.jar[u.Host] } func Auth(username string, password string, uri string) (bool, error) { client := &http.Client{} jar := &myjar{} jar.jar = make(map[string][]*http.Cookie) client.Jar = jar var req *http.Request var resp *http.Response var err error req, err = http.NewRequest("GET", uri, nil) resp, err = client.Do(req) if err != nil { return false, err } if resp.StatusCode == 401 { var authorization map[string]string = DigestAuthParams(resp) realmHeader := authorization["realm"] qopHeader := authorization["qop"] nonceHeader := authorization["nonce"] opaqueHeader := authorization["opaque"] realm := realmHeader // A1 h := md5.New() A1 := fmt.Sprintf("%s:%s:%s", username, realm, password) io.WriteString(h, A1) HA1 := fmt.Sprintf("%x", h.Sum(nil)) // A2 h = md5.New() A2 := fmt.Sprintf("GET:%s", "/auth") io.WriteString(h, A2) HA2 := fmt.Sprintf("%x", h.Sum(nil)) // response cnonce := RandomKey() response := H(strings.Join([]string{HA1, nonceHeader, "00000001", cnonce, qopHeader, HA2}, ":")) // now make header AuthHeader := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=00000001, qop=%s, response="%s", opaque="%s", algorithm=MD5`, username, realmHeader, nonceHeader, "/auth", cnonce, qopHeader, response, opaqueHeader) req.Header.Set("Authorization", AuthHeader) resp, err = client.Do(req) } else { return false, fmt.Errorf("response status code should have been 401, it was %v", resp.StatusCode) } return resp.StatusCode == 200, err } /* Parse Authorization header from the http.Request. Returns a map of auth parameters or nil if the header is not a valid parsable Digest auth header. */ func DigestAuthParams(r *http.Response) map[string]string { s := strings.SplitN(r.Header.Get("Www-Authenticate"), " ", 2) if len(s) != 2 || s[0] != "Digest" { return nil } result := map[string]string{} for _, kv := range strings.Split(s[1], ",") { parts := strings.SplitN(kv, "=", 2) if len(parts) != 2 { continue } result[strings.Trim(parts[0], "\" ")] = strings.Trim(parts[1], "\" ") } return result } 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)) }